Wormhole Style Anonymouse File Transfer Design
I want to design a magic-wormhole-style file transfer system that is anonymous and secure. This is a design document for the system.
However instead of using magic-wormhole's rendezvous server and protocol, we will use WebRTC.
Requirements
- Use WebRTC
- Design should be as simple as possible, no auth required
- The connection should be atomic, don't prepare anything when the other side isn't ready
- Supports both text and file
Usage
xc send <file>
# gives you a code: e.g. 5-great-fox
xc receive <code>
# downloads the file
Algorithm
Clint 1 runs
xc send <file>- CrossCopy reads file info (size, name,
shasum) and send a file transfer request (HTTP) to the servershasumwill be used later to verify the file integrity in the destinationshasumcan be computed asynchronously in the background and upload to the server later. In case of a large file, the client can start uploading the file while the server is computing theshasum.- Client info should also be included (e.g. platform). This is because web platform has size limitation (max 200MB)
- CrossCopy reads file info (size, name,
Server generates an unique passphrase (e.g. 5-great-fox), and creates a new file transfer session in database (e.g. redis)
- store the file info in the session (e.g. redis hashes or json)
hset anonymous:file-transfer:5-great-fox size 1234
hset anonymous:file-transfer:5-great-fox name app.zip
hset anonymous:file-transfer:5-great-fox shasum 2b40c047855d459576fd685d058fe21c1278808b
EXPIRE anonymous:file-transfer:5-great-fox 600 # 10 minutes expiryServer sends the passphrase back to client 1
Client 1 displays passphrase, url to copy and QR Code
- User 1 should somehow tell User 2 the passphrase
Client 1 connects to server through Socket.io and wait for Client 2
- Use a custom socketio namespace (e.g. anonymous-file-transfer)
- Connection handshake query should include the passphrase
- Listen to
receiver-onlineevent, once receiver is online, start connection and transfer. let's call itstart()function
Server get's connection from client 1
Store socketid in database
hset anonymous:file-transfer:5-great-fox sender-socketid <socket-id>Wait for Client 2's connection
Client 2 runs
xc receive 5-great-fox- CrossCopy sends a post request with the passprase in request body to get file transfer information
- If Client 2 is web platform and the file size exceeds the limit, the server should return an error to client 2
- CrossCopy connects to server with socket.io and the passphrase in handshake query
- CrossCopy sends a post request with the passprase in request body to get file transfer information
Servers receives connection
Look for the passphrase in database
- If not found, return an error to client 2 and close connection
- If found, store the socketid in database
hset anonymous:file-transfer:5-great-fox receiver-socketid <socket-id>- Send
receiver-onlineevent to client 1
Client 1 receives
receiver-onlineevent andstart()- Create
RTCPeerConnection,RTCDataChanneland offer - Set up ice candidate listener
- Set offer to local description
- Emit
offerevent to client 2 (server simply forwards the event to client 2) - Wait for
answerevent from client 2
- Create
Client 2 receives
offereventCreate
RTCPeerConnection,RTCDataChanneland answer- Also set up ice candidate listener
Set offer to remote description
Set answer to local description
Emit
answerevent to client 1 (server simply forwards the event to client 1)Client 1 receives
answerevent- Set answer to remote description
- When data channel's on open is triggered
- Use something like
BufReaderto read file chunk by chunk (huge file may overflow memory) - For every chunk read, send the data through
RTCDataChannel - After all chunks are sent, close the data channel.
- Consider whether data loss is possible if the data channel is closed before all data is received.
- If possible, wait for a socketio event from client 2 to close the data channel. Client 2 knows the file size and can verify if all data is received. But it's possible the transfer hangs as client 2 keeps waiting for new data if unexpected error happens. Think carefully.
- Use something like
Client 2 receives file data
- When on message triggered
- Record the timestamp the first chunk arrives
- Append received data to the target file
- Accumulate the received data size
- If the accumulated data size is equal to the file size, the file transfer is complete, but let's wait for data channel close event
- After data channel closed
- Compare the received file size with the expected file size.
- If they match, the file transfer is successful. Emit a
successevent to Client 1, and display a message. - If not, there was an error in the file transfer. Emit a
failureevent to Client 1, and display a message.
- If they match, the file transfer is successful. Emit a
- Verify file integrity with
shasum- Display a message saying the file is being verified.
- Emit
successorfailureevent to Client 1 and display a message.
- When on message triggered
Once the file transfer is complete
- Close
RTCPeerConnection. - Close the socket.io connection.
- Close
Events
receiver-onlineofferanswersuccessfailureverify-successverify-failureerror
Further Considerations
Multiple File Transfer Design Using the Same Connection
Same RTCPeerConnection
For transferring multiple files using the same RTCPeerConnection, we can consider the following design:
Client 1 sends a list of files (with their metadata) to Client 2 before starting the transfer. This can be done using the
RTCDataChannel.Client 2 acknowledges the receipt of the file list and prepares for receiving multiple files.
Client 1 starts sending files one by one. For each file, it sends a 'start-file' message with the file's metadata (name, size, etc.) before sending the file data.
Client 2, upon receiving a 'start-file' message, prepares to receive a new file and starts writing the incoming data to a new file.
Once a file is completely transferred, Client 1 sends an 'end-file' message. Client 2, upon receiving this message, finalizes the current file and prepares for the next one.
This process continues until all files are transferred. After the last file, Client 1 sends an 'end-transfer' message to signal the end of the transfer.
Client 2, upon receiving the 'end-transfer' message, finalizes the last file and closes the
RTCDataChannel.
This design allows for the transfer of multiple files over a single RTCPeerConnection, but it also introduces additional complexity. Error handling becomes more important and complex, as errors can now occur at the file level as well as at the connection level. For example, if a file fails to transfer correctly, the system needs to decide whether to abort the entire transfer or to skip the problematic file and continue with the next one.