Files
simplex-chat/apps/ios/spec/services/files.md
2026-02-19 10:58:16 +00:00

19 KiB

SimpleX Chat iOS -- File Transfer Service

Technical specification for file transfer: inline/XFTP protocols, auto-receive thresholds, CryptoFile encryption, and file constants.

Related specs: Compose Module | Chat View | API Reference | Database | README Related product: Product Overview

Source: FileUtils.swift | CryptoFile.swift | ChatTypes.swift | AppAPITypes.swift | SimpleXAPI.swift


Table of Contents

  1. Overview
  2. Transfer Methods
  3. Auto-Receive Thresholds
  4. File Size Constants
  5. Image Handling
  6. Voice Messages
  7. CryptoFile -- At-Rest Encryption
  8. File Storage Paths
  9. File Lifecycle
  10. API Commands

1. Overview

SimpleX Chat supports two file transfer methods depending on file size:

File ≤ 255KB (inline)
├── Base64 encoded directly in SMP message
├── Single message delivery
└── No extra server infrastructure needed

File > 255KB up to 1GB (XFTP)
├── Encrypted and chunked
├── Uploaded to XFTP relay servers
├── Recipient downloads chunks from relays
└── Files auto-deleted from relays after download or expiry

All files are end-to-end encrypted. The XFTP protocol adds a second encryption layer on top of the SMP channel encryption.


2. Transfer Methods

Inline Transfer

  • Files up to MAX_IMAGE_SIZE (255KB) are base64-encoded and embedded directly in the SMP message body
  • No additional protocol or server needed
  • Delivered with the same reliability guarantees as regular messages
  • Used primarily for compressed images

XFTP Transfer

For files exceeding the inline threshold (up to MAX_FILE_SIZE_XFTP = 1GB):

  1. Sender side:

    • File is AES-encrypted with a random key
    • Encrypted file is split into chunks
    • Chunks are uploaded to one or more XFTP relay servers
    • File metadata (key, chunk locations) sent to recipient via SMP message
  2. Recipient side:

    • Receives file metadata via SMP
    • Downloads chunks from XFTP relays
    • Reassembles and decrypts the file
  3. Cleanup:

    • XFTP relays delete chunks after download or after expiry period
    • No persistent storage on relays

SMP Transfer (legacy)

MAX_FILE_SIZE_SMP (8MB) exists as a constant for larger inline transfers through SMP, used in specific scenarios.


3. Auto-Receive Thresholds

Files below certain size thresholds are automatically accepted and downloaded without user confirmation:

Media Type Auto-Receive Threshold Constant Line
Images 510 KB MAX_IMAGE_SIZE_AUTO_RCV L21
Voice messages 510 KB MAX_VOICE_SIZE_AUTO_RCV L24
Video 1023 KB MAX_VIDEO_SIZE_AUTO_RCV L27
Other files Not auto-received Requires manual acceptance --

Behavior

Relay Approval

userApprovedRelays parameter: when the file is hosted on relays not in the user's configured server list, the user is asked for confirmation before connecting to unknown relays.


4. File Size Constants

Defined in SimpleXChat/FileUtils.swift:

Constant Value Line
MAX_IMAGE_SIZE 261,120 (255 KB) L18
MAX_IMAGE_SIZE_AUTO_RCV 522,240 (510 KB) L21
MAX_VOICE_SIZE_AUTO_RCV 522,240 (510 KB) L24
MAX_VIDEO_SIZE_AUTO_RCV 1,047,552 (1023 KB) L27
MAX_FILE_SIZE_XFTP 1,073,741,824 (1 GB) L30
MAX_FILE_SIZE_LOCAL Int64.max (no limit) L32
MAX_FILE_SIZE_SMP 8,000,000 (~7.6 MB) L34
MAX_VOICE_MESSAGE_LENGTH 300 s (5 min) L36
// Image compression target for inline transfer
public let MAX_IMAGE_SIZE: Int64 = 261_120           // 255 KB

// Auto-receive thresholds
public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = 522_240  // 510 KB (2 * MAX_IMAGE_SIZE)
public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = 522_240  // 510 KB (2 * MAX_IMAGE_SIZE)
public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023 KB

// Transfer method limits
public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1 GB
public let MAX_FILE_SIZE_SMP: Int64 = 8_000_000       // ~7.6 MB
public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max      // No limit (local notes)

// Voice message constraints
public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes (300 seconds)

5. Image Handling

Compression Pipeline

  1. User selects image (camera or photo library)
  2. Image is compressed to fit within MAX_IMAGE_SIZE (255KB):
    • Progressive JPEG compression with decreasing quality
    • Resize if dimensions are too large
  3. Compressed image is base64-encoded into the message content
  4. For larger images that cannot compress to 255KB: sent via XFTP

Display

  • CIImageView renders images in chat bubbles with aspect-fit sizing
  • Tapping opens FullScreenMediaView with zoom/pan/share capabilities
  • Thumbnail is displayed immediately; full-size loaded on demand for XFTP images

Animated Images

  • GIFs are handled by AnimatedImageView
  • Displayed inline with animation support

6. Voice Messages

Recording

  1. ComposeVoiceView manages the recording UI
  2. AudioRecPlay handles AVAudioRecorder lifecycle
  3. Recorded in compressed audio format
  4. Maximum duration: MAX_VOICE_MESSAGE_LENGTH = 300 seconds (5 minutes)
  5. Waveform data extracted for visualization

Transfer

  • Voice files up to MAX_VOICE_SIZE_AUTO_RCV (510KB) are auto-received
  • Larger voice files follow standard file transfer flow
  • Voice messages include waveform metadata for UI rendering

Playback

  • CIVoiceView / FramedCIVoiceView render voice messages
  • Shows waveform visualization and play/pause control
  • ChatModel.stopPreviousRecPlay ensures only one audio source plays at a time
  • Playback position and progress tracked

7. CryptoFile -- At-Rest Encryption

When apiSetEncryptLocalFiles(enable: true) is configured, files stored on the device are AES-encrypted.

CryptoFile Type

struct CryptoFile {
    var filePath: String
    var cryptoArgs: CryptoFileArgs?   // nil = unencrypted
}

struct CryptoFileArgs {
    var fileKey: String       // AES encryption key
    var fileNonce: String     // AES nonce/IV
}

Defined in ChatTypes.swift L4241 (CryptoFile) and L4289 (CryptoFileArgs).

Encryption Operations (C FFI)

Implemented in CryptoFile.swift:

Function Purpose Line
writeCryptoFile Write encrypted file, returns CryptoFileArgs L18
readCryptoFile Read and decrypt file, returns Data L31
encryptCryptoFile Encrypt existing file to new path L54
decryptCryptoFile Decrypt file to new path L66

Storage

  • Encrypted files stored alongside unencrypted files in Documents/files/
  • The CryptoFileArgs (key + nonce) are stored in the Haskell database, not on the filesystem
  • Toggle via privacy settings: apiSetEncryptLocalFiles(enable:)

8. File Storage Paths

Directory Structure

Function Path Line
getAppFilesDirectory() Documents/files/ L208
getTempFilesDirectory() Documents/temp_files/ L199
getWallpaperDirectory() Documents/wallpapers/ L217
getAppFilePath(_:) Documents/files/{filename} L212
getWallpaperFilePath(_:) Documents/wallpapers/{filename} L221
func getAppFilesDirectory() -> URL    // Documents/files/
func getTempFilesDirectory() -> URL   // Documents/temp_files/
func getWallpaperDirectory() -> URL   // Documents/wallpapers/

Path Management


9. File Lifecycle

Sending

1. User selects file/image/video in compose
2. ComposeView creates ComposedMessage with file reference
3. apiSendMessages() → Haskell core processes:
   a. File ≤ inline threshold: base64 encode into message
   b. File > inline threshold: start XFTP upload
4. Upload events:
   - ChatEvent.sndFileStart
   - ChatEvent.sndFileProgressXFTP (periodic progress)
   - ChatEvent.sndFileCompleteXFTP (upload done)
   - ChatEvent.sndFileError (on failure)

Receiving

1. Message with file attachment arrives
2. Auto-receive check:
   a. Below threshold: automatic download starts
   b. Above threshold: user sees download button
3. User triggers download (or auto-triggered):
   - receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)
4. Download events:
   - ChatEvent.rcvFileStart
   - ChatEvent.rcvFileProgressXFTP (periodic progress)
   - ChatEvent.rcvFileComplete (download done)
   - ChatEvent.rcvFileError (on failure)
   - ChatEvent.rcvFileSndCancelled (sender cancelled)

Cancellation

ChatCommand.cancelFile(fileId: Int64)

Cancels an in-progress upload or download. For XFTP transfers, also requests chunk deletion from relays.

Cleanup

Function Purpose Line
cleanupFile(_:) Remove file associated with a chat item L267
cleanupDirectFile(_:) Remove file only for direct chats L260
removeFile(_:) Delete file at URL L243
removeFile(_:) Delete file by name L251
deleteAppFiles() Remove all app files (preserving databases) L108
deleteAppDatabaseAndFiles() Remove everything L86
  • When a ChatItem is deleted, its associated file is deleted from disk
  • When a timed message expires, its file is deleted
  • ChatModel.filesToDelete queues files for deferred deletion
  • deleteAppFiles() removes all files (preserving databases)
  • deleteAppDatabaseAndFiles() removes everything

10. API Commands

Command Parameters Description Line
receiveFile fileId, userApprovedRelays, encrypted, inline Accept and start downloading a file L167
setFileToReceive fileId, userApprovedRelays, encrypted Mark file for auto-receive (no immediate download) L168
cancelFile fileId Cancel in-progress transfer L169
apiUploadStandaloneFile userId, file: CryptoFile Upload file to XFTP without a chat context L179
apiDownloadStandaloneFile userId, url, file: CryptoFile Download from XFTP URL L180
apiStandaloneFileInfo url Get metadata for an XFTP URL L181

File Transfer Events

Event Description Line
rcvFileAccepted Download request accepted L1095
rcvFileStart Download started L1097
rcvFileProgressXFTP Download progress (receivedSize, totalSize) L1098
rcvFileComplete Download complete L1099
rcvFileSndCancelled Sender cancelled the transfer L1101
rcvFileError Download failed L1102
rcvFileWarning Download warning (non-fatal) L1103
sndFileStart Upload started L1105
sndFileComplete Inline upload complete L1106
sndFileProgressXFTP XFTP upload progress (sentSize, totalSize) L1108
sndFileCompleteXFTP XFTP upload complete L1110
sndFileRcvCancelled Receiver cancelled L1107
sndFileError Upload failed L1112
sndFileWarning Upload warning (non-fatal) L1113

Source Files

File Path Key Definitions
File utilities & constants SimpleXChat/FileUtils.swift MAX_IMAGE_SIZE, saveFile, removeFile, getMaxFileSize
CryptoFile FFI operations SimpleXChat/CryptoFile.swift writeCryptoFile, readCryptoFile, encryptCryptoFile, decryptCryptoFile
CryptoFile / CryptoFileArgs types SimpleXChat/ChatTypes.swift CryptoFile (L4241), CryptoFileArgs (L4289)
API command definitions Shared/Model/AppAPITypes.swift receiveFile, cancelFile, ChatEvent file events
API implementations Shared/Model/SimpleXAPI.swift receiveFile (L1471), cancelFile (L1590)
File view (chat item) Shared/Views/Chat/ChatItem/CIFileView.swift
Image view (chat item) Shared/Views/Chat/ChatItem/CIImageView.swift
Video view (chat item) Shared/Views/Chat/ChatItem/CIVideoView.swift
Voice view (chat item) Shared/Views/Chat/ChatItem/CIVoiceView.swift
Compose file preview Shared/Views/Chat/ComposeMessage/ComposeFileView.swift
Compose image preview Shared/Views/Chat/ComposeMessage/ComposeImageView.swift
Compose voice preview Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift
C FFI (file encryption) SimpleXChat/SimpleX.h chat_write_file, chat_read_file, chat_encrypt_file, chat_decrypt_file
Haskell file logic ../../src/Simplex/Chat/Files.hs --
Haskell file store ../../src/Simplex/Chat/Store/Files.hs --