Files
simplex-chat/apps/ios/product/flows/file-transfer.md
T

210 lines
10 KiB
Markdown

# File Transfer Flow
> **Related spec:** [spec/services/files.md](../../spec/services/files.md)
## Overview
File and media sharing in SimpleX Chat iOS. Small files are sent inline within SMP messages; large files use the XFTP (eXtended File Transfer Protocol) for chunked, encrypted uploads up to 1GB. All files are encrypted end-to-end. Optional local encryption protects downloaded files at rest using AES via `CryptoFile`.
## Prerequisites
- Established contact or group conversation
- For sending: photo library or file picker access permission
- For receiving: sufficient device storage
- XFTP relay servers configured (default servers or custom)
## Size Limits
| Category | Limit | Constant |
|----------|-------|----------|
| Inline image (compressed) | 255 KB | `MAX_IMAGE_SIZE` = 261,120 bytes |
| Auto-receive image | 510 KB | `MAX_IMAGE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
| Auto-receive voice | 510 KB | `MAX_VOICE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
| Auto-receive video | 1,023 KB | `MAX_VIDEO_SIZE_AUTO_RCV` = 1,047,552 bytes |
| Max file via XFTP | 1 GB | `MAX_FILE_SIZE_XFTP` = 1,073,741,824 bytes |
| Max file via SMP | ~8 MB | `MAX_FILE_SIZE_SMP` = 8,000,000 bytes |
| Max voice message length | 5 min | `MAX_VOICE_MESSAGE_LENGTH` = 300s |
## Step-by-Step Processes
### 1. Send Image
1. User taps the attachment button in `ComposeView` and selects an image.
2. `ComposeImageView` displays the selected image preview.
3. Image is compressed to fit within `MAX_IMAGE_SIZE` (255KB).
4. `ComposedMessage` is built:
```swift
ComposedMessage(
fileSource: CryptoFile(filePath: compressedImagePath),
msgContent: .image(text: captionText, image: base64Thumbnail)
)
```
5. `apiSendMessages(type:id:scope:composedMessages:)` is called.
6. For images <=255KB: sent inline within the SMP message.
7. For larger images: XFTP upload is used (see XFTP transfer below).
8. Recipient auto-receives images up to 510KB (`MAX_IMAGE_SIZE_AUTO_RCV`).
### 2. Send Video
1. User picks a video from the library.
2. Thumbnail is generated from the first frame.
3. Video duration is calculated.
4. `ComposedMessage` is built:
```swift
ComposedMessage(
fileSource: CryptoFile(filePath: videoFilePath),
msgContent: .video(text: captionText, image: base64Thumbnail, duration: durationSeconds)
)
```
5. `apiSendMessages(...)` is called.
6. Video files are typically >255KB, so XFTP upload is used.
7. Recipient auto-receives videos up to 1,023KB (`MAX_VIDEO_SIZE_AUTO_RCV`).
8. `CIVideoView` displays thumbnail with play button; video downloads on tap if not auto-received.
### 3. Send File
1. User taps the attachment button and selects a document via the system file picker.
2. `ComposeFileView` shows the file name and size.
3. `ComposedMessage` is built:
```swift
ComposedMessage(
fileSource: CryptoFile(filePath: filePath),
msgContent: .file(fileName)
)
```
4. `apiSendMessages(...)` is called.
5. If file <=255KB: sent inline via SMP.
6. If file >255KB and <=1GB: uploaded via XFTP.
7. Files >1GB: rejected (prevented in UI).
8. `CIFileView` displays file icon, name, and size for the recipient.
### 4. Send Voice Message
1. User taps and holds the microphone button in `ComposeView`.
2. `AudioRecPlay` records audio to a temporary file.
3. `ComposeVoiceView` shows recording waveform and duration.
4. On release (or tapping stop), recording ends.
5. Duration is checked against `MAX_VOICE_MESSAGE_LENGTH` (5 minutes / 300 seconds).
6. `ComposedMessage` is built:
```swift
ComposedMessage(
fileSource: CryptoFile(filePath: voiceFilePath),
msgContent: .voice(text: "", duration: durationSeconds)
)
```
7. `apiSendMessages(...)` is called.
8. Voice messages <=510KB are sent inline.
9. Recipient auto-receives voice up to 510KB (`MAX_VOICE_SIZE_AUTO_RCV`).
10. `CIVoiceView` renders waveform with playback controls.
### 5. Receive File
1. Core receives a message with a file reference via SMP.
2. `ChatEvent.newChatItems` delivers the chat item with file metadata.
3. Auto-receive logic checks:
- File type and size against auto-receive thresholds.
- User's auto-receive preferences.
4. If auto-received or user taps "Download":
```swift
func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async
```
5. Internally calls `receiveFiles(user:fileIds:userApprovedRelays:auto:)`.
6. Sends `ChatCommand.receiveFile(fileId:userApprovedRelays:encrypted:inline:)`.
7. `encrypted` is determined by `privacyEncryptLocalFilesGroupDefault`.
8. `userApprovedRelays` controls whether unknown XFTP relay servers are trusted.
9. On success: `ChatResponse2.rcvFileAccepted(user, chatItem)` -- file download begins.
10. On sender cancelled: `ChatResponse2.rcvFileAcceptedSndCancelled(user, rcvFileTransfer)`.
11. Download progress is tracked and shown in the UI.
12. Completed files are stored in the app's `Documents/files/` directory.
### 6. XFTP Transfer (Large Files)
**Upload (sender side):**
1. File is encrypted locally with a random symmetric key.
2. Encrypted file is split into chunks.
3. Chunks are uploaded to one or more XFTP relay servers.
4. A file description (URI with encryption key and chunk locations) is created.
5. The file description is sent to the recipient via the SMP message.
**Download (recipient side):**
1. Recipient receives the file description via SMP.
2. Chunks are downloaded from XFTP relay servers.
3. Chunks are reassembled and decrypted locally.
4. File is available at the local path.
**Standalone file operations** (used for database migration):
- `uploadStandaloneFile(user:file:ctrl:)` -- upload without a chat message
- `downloadStandaloneFile(user:url:file:ctrl:)` -- download from a standalone URL
- `standaloneFileInfo(url:ctrl:)` -- get metadata for a standalone file URL
### 7. Local File Encryption
1. If `privacyEncryptLocalFilesGroupDefault` is enabled in privacy settings:
- Downloaded files are encrypted at rest using AES via `CryptoFile`.
- `CryptoFile` wraps a file path with encryption metadata.
2. Encryption key is derived and stored securely.
3. Files are decrypted on-the-fly when accessed for viewing/playback.
4. This protects files even if the device storage is accessed externally.
### 8. Unknown Relay Server Approval
1. When receiving a file, XFTP relay servers are checked against known/approved servers.
2. If unknown servers are detected: `ChatError.error(.fileNotApproved(fileId, unknownServers))`.
3. If not auto-receiving, user is shown an alert:
- "Unknown servers! Without Tor or VPN, your IP address will be visible to these XFTP relays: [server list]."
- Option to "Download" (approve) or cancel.
4. On approval: `receiveFiles(user:fileIds:userApprovedRelays: true)` retries with approval.
5. If `privacyAskToApproveRelaysGroupDefault` is disabled, relays are auto-approved.
## Data Structures
| Type | Location | Description |
|------|----------|-------------|
| `CryptoFile` | `SimpleXChat/CryptoFile.swift` | File path with optional encryption key and nonce for local AES encryption |
| `MsgContent.image` | `SimpleXChat/ChatTypes.swift` | `.image(text: String, image: String)` -- text caption + base64 thumbnail |
| `MsgContent.video` | `SimpleXChat/ChatTypes.swift` | `.video(text: String, image: String, duration: Int)` -- caption + thumbnail + duration |
| `MsgContent.voice` | `SimpleXChat/ChatTypes.swift` | `.voice(text: String, duration: Int)` -- empty text + duration in seconds |
| `MsgContent.file` | `SimpleXChat/ChatTypes.swift` | `.file(String)` -- file name |
| `ComposedMessage` | `SimpleXChat/APITypes.swift` | Outgoing message with fileSource, quotedItemId, msgContent, mentions |
| `FileTransferMeta` | `SimpleXChat/ChatTypes.swift` | Metadata for an ongoing file transfer |
| `RcvFileTransfer` | `SimpleXChat/ChatTypes.swift` | State of a file being received |
| `MigrationFileLinkData` | Used for standalone file transfers during database migration |
## Error Cases
| Error | Cause | Handling |
|-------|-------|----------|
| `fileNotApproved(fileId, unknownServers)` | Unknown XFTP relay servers | Alert with option to approve and retry |
| `fileCancelled` | File transfer was cancelled | Silently ignored in `receiveFiles` |
| `fileAlreadyReceiving` | Duplicate receive request | Silently ignored |
| `rcvFileAcceptedSndCancelled` | Sender cancelled after acceptance | Alert: "Sender cancelled file transfer" |
| File too large | Exceeds 1GB XFTP limit | Prevented in UI picker |
| Network errors | XFTP server unreachable | Standard retry mechanism |
| Storage full | Insufficient device storage | System-level error |
## Key Files
| File | Purpose |
|------|---------|
| `SimpleXChat/FileUtils.swift` | File size constants, path utilities, database file management |
| `SimpleXChat/CryptoFile.swift` | Local file encryption/decryption with AES |
| `SimpleXChat/ImageUtils.swift` | Image compression and thumbnail generation |
| `Shared/Views/Chat/ComposeMessage/ComposeView.swift` | File/media attachment selection and composition |
| `Shared/Views/Chat/ComposeMessage/ComposeImageView.swift` | Image preview in compose area |
| `Shared/Views/Chat/ComposeMessage/ComposeFileView.swift` | File preview in compose area |
| `Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift` | Voice recording UI with waveform |
| `Shared/Views/Chat/ChatItem/CIFileView.swift` | File message display: icon, name, size, download action |
| `Shared/Views/Chat/ChatItem/CIImageView.swift` | Image message display: thumbnail, full-screen tap |
| `Shared/Views/Chat/ChatItem/CIVideoView.swift` | Video message display: thumbnail, play button, inline playback |
| `Shared/Views/Chat/ChatItem/CIVoiceView.swift` | Voice message display: waveform, playback controls |
| `Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift` | Voice message inside a framed (quoted/forwarded) context |
| `Shared/Views/Chat/ChatItem/FullScreenMediaView.swift` | Full-screen image/video viewer |
| `Shared/Model/SimpleXAPI.swift` | `apiSendMessages`, `receiveFile`, `receiveFiles`, `uploadStandaloneFile`, `downloadStandaloneFile` |
| `Shared/Model/AudioRecPlay.swift` | Audio recording and playback engine for voice messages |
## Related Specifications
- `apps/ios/product/README.md` -- Product overview: Messaging capability (file sharing)
- `apps/ios/product/flows/messaging.md` -- File transfer is part of the message send flow
- `apps/ios/product/views/chat.md` -- Chat view file/media display