mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-15 04:50:08 +00:00
210 lines
10 KiB
Markdown
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
|