# File Transfer Service
## Table of Contents
1. [Overview](#1-overview)
2. [File Size Constants](#2-file-size-constants)
3. [CryptoFile](#3-cryptofile)
4. [File Storage Paths](#4-file-storage-paths)
5. [API Commands](#5-api-commands)
6. [Auto-Receive Logic](#6-auto-receive-logic)
7. [Source Files](#7-source-files)
## Executive Summary
SimpleX Chat uses two file transfer mechanisms: inline SMP transfers for small files (embedded in message bodies) and XFTP (eXtended File Transfer Protocol) for larger files up to 1 GB. Files are optionally encrypted at rest using `CryptoFile` functions backed by the chat core's native crypto library. File storage paths are platform-specific: Android uses `Context.dataDir`-based directories while Desktop uses platform-appropriate data directories (XDG on Linux, AppData on Windows, Application Support on macOS). Auto-receive logic automatically accepts images, voice messages, and videos below configurable size thresholds.
---
## 1. Overview
File transfer decision logic:
- **Inline (SMP)**: Files small enough to be base64-encoded and embedded directly in an SMP message body. The practical limit is defined by `MAX_IMAGE_SIZE` (255 KB) for compressed images. The maximum SMP inline size is `MAX_FILE_SIZE_SMP` (~7.6 MB).
- **XFTP**: For files exceeding the inline threshold, up to `MAX_FILE_SIZE_XFTP` (1 GB). XFTP uses dedicated file relay servers with chunked, encrypted transfers.
The `receiveFile` / `receiveFiles` API commands handle both protocols transparently -- the chat core selects the appropriate transfer mechanism based on file metadata received from the sender.
---
## 2. File Size Constants
Defined in [`Utils.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L118):
| Constant | Value | Human-Readable | Line | Purpose |
|---|---|---|---|---|
| `MAX_IMAGE_SIZE` | 261,120 | 255 KB | [L118](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L118) | Inline image compression target |
| `MAX_IMAGE_SIZE_AUTO_RCV` | 522,240 | 510 KB | [L119](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L119) | Auto-receive threshold for images (`2 * MAX_IMAGE_SIZE`) |
| `MAX_VOICE_SIZE_AUTO_RCV` | 522,240 | 510 KB | [L120](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L120) | Auto-receive threshold for voice messages (`2 * MAX_IMAGE_SIZE`) |
| `MAX_VIDEO_SIZE_AUTO_RCV` | 1,047,552 | 1023 KB | [L121](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L121) | Auto-receive threshold for video |
| `MAX_VOICE_MILLIS_FOR_SENDING` | 300,000 | 5 min | [L123](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L123) | Maximum voice message duration |
| `MAX_FILE_SIZE_SMP` | 8,000,000 | ~7.6 MB | [L125](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L125) | Maximum SMP inline file size |
| `MAX_FILE_SIZE_XFTP` | 1,073,741,824 | 1 GB | [L127](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L127) | Maximum XFTP transfer size |
| `MAX_FILE_SIZE_LOCAL` | `Long.MAX_VALUE` | Unlimited | [L129](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L129) | Local file protocol (no size limit) |
The `getMaxFileSize()` function ([`Utils.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt#L442)) selects the limit based on `FileProtocol`:
```kotlin
FileProtocol.XFTP -> MAX_FILE_SIZE_XFTP
FileProtocol.SMP -> MAX_FILE_SIZE_SMP
FileProtocol.LOCAL -> MAX_FILE_SIZE_LOCAL
```
---
## 3. CryptoFile
[`CryptoFile.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt) (62 lines)
Provides encrypted file I/O backed by the chat core's native cryptography (via JNI/JNA calls to `chatWriteFile`, `chatReadFile`, `chatEncryptFile`, `chatDecryptFile`).
### Data types
```kotlin
@Serializable
sealed class WriteFileResult {
@SerialName("result") data class Result(val cryptoArgs: CryptoFileArgs): WriteFileResult()
@SerialName("error") data class Error(val writeError: String): WriteFileResult()
}
```
`CryptoFileArgs` contains `fileKey` and `fileNonce` -- the symmetric encryption key and nonce for AES-GCM encryption.
### Functions
| Function | Line | Signature | Description |
|---|---|---|---|
| `writeCryptoFile` | [L24](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt#L24) | `(path: String, data: ByteArray): CryptoFileArgs` | Writes data to an encrypted file via a direct `ByteBuffer`. Returns the generated key and nonce. Requires initialized `ChatController`. |
| `readCryptoFile` | [L36](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt#L36) | `(path: String, cryptoArgs: CryptoFileArgs): ByteArray` | Reads and decrypts a file given its key and nonce. Returns the plaintext bytes. Throws on error (status != 0). |
| `encryptCryptoFile` | [L47](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt#L47) | `(fromPath: String, toPath: String): CryptoFileArgs` | Encrypts an existing plaintext file to a new encrypted file. Returns the generated key and nonce. |
| `decryptCryptoFile` | [L57](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt#L57) | `(fromPath: String, cryptoArgs: CryptoFileArgs, toPath: String)` | Decrypts an encrypted file to a plaintext output file. Throws on non-empty error string. |
All functions delegate to native C library functions through the chat core JNI bridge.
---
## 4. File Storage Paths
### Common expect declarations
[`Files.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt) (191 lines, commonMain)
| Property | Line | Description |
|---|---|---|
| `dataDir` | [L18](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L18) | Root application data directory |
| `tmpDir` | [L19](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L19) | Temporary files directory |
| `filesDir` | [L20](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L20) | Base files directory |
| `appFilesDir` | [L21](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L21) | Application files (chat attachments) |
| `wallpapersDir` | [L22](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L22) | Theme wallpaper images |
| `coreTmpDir` | [L23](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L23) | Temporary files for the chat core |
| `dbAbsolutePrefixPath` | [L24](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L24) | Database file path prefix |
| `preferencesDir` | [L25](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L25) | Preferences/config directory |
| `databaseExportDir` | [L35](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L35) | Temporary DB archive storage for export |
| `remoteHostsDir` | [L37](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L37) | Remote host connection data |
### Android implementation
[`Files.android.kt`](../../common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt) (79 lines)
| Property | Value |
|---|---|
| `dataDir` | `androidAppContext.dataDir` |
| `tmpDir` | `androidAppContext.getDir("temp", MODE_PRIVATE)` |
| `filesDir` | `dataDir/files` |
| `appFilesDir` | `dataDir/files/app_files` |
| `wallpapersDir` | `dataDir/files/assets/wallpapers` |
| `coreTmpDir` | `dataDir/files/temp_files` |
| `dbAbsolutePrefixPath` | `dataDir/files` |
| `preferencesDir` | `dataDir/shared_prefs` |
| `databaseExportDir` | `androidAppContext.cacheDir` |
| `remoteHostsDir` | `tmpDir/remote_hosts` |
### Desktop implementation
[`Files.desktop.kt`](../../common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt) (116 lines)
| Property | Value |
|---|---|
| `dataDir` | `desktopPlatform.dataPath` (XDG_DATA_HOME on Linux, AppData on Windows, Application Support on macOS) |
| `tmpDir` | `java.io.tmpdir/simplex` (deleted on exit) |
| `filesDir` | `dataDir/simplex_v1_files` |
| `appFilesDir` | Same as `filesDir` |
| `wallpapersDir` | `dataDir/simplex_v1_assets/wallpapers` |
| `coreTmpDir` | `dataDir/tmp` |
| `dbAbsolutePrefixPath` | `dataDir/simplex_v1` |
| `preferencesDir` | `desktopPlatform.configPath` |
| `databaseExportDir` | Same as `tmpDir` |
| `remoteHostsDir` | `dataDir/remote_hosts` |
### Helper functions (common)
| Function | Line | Description |
|---|---|---|
| `getAppFilePath` | [L81](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L81) | Resolves file path considering remote hosts |
| `getWallpaperFilePath` | [L91](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L91) | Resolves wallpaper image path, creates parent directories |
| `getLoadedFilePath` | [L105](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L105) | Returns path if file exists and is fully loaded |
| `getLoadedFileSource` | [L115](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L115) | Returns `CryptoFile` source if file is loaded |
| `readThemeOverrides` | [L125](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L125) | Reads theme overrides from `themes.yaml` |
| `writeThemeOverrides` | [L151](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L151) | Atomically writes theme overrides to `themes.yaml` |
| `copyFileToFile` | [L47](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L47) | Copies a `File` to a `URI` destination with toast feedback |
| `copyBytesToFile` | [L63](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt#L63) | Copies a `ByteArrayInputStream` to a `URI` destination |
---
## 5. API Commands
Defined in [`SimpleXAPI.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt):
| Function | Line | Signature | Description |
|---|---|---|---|
| `receiveFiles` | [L1946](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1946) | `(rhId, user, fileIds, userApprovedRelays, auto)` | Receive multiple files. Sends `CC.ReceiveFile` for each ID. Handles relay approval workflow: collects unapproved files, shows alert, re-calls with `userApprovedRelays=true`. Respects `privacyEncryptLocalFiles` preference. |
| `receiveFile` | [L2062](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2062) | `(rhId, user, fileId, userApprovedRelays, auto)` | Delegates to `receiveFiles` with a single-element list. |
| `cancelFile` | [L2072](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2072) | `(rh, user, fileId)` | Cancels an in-progress file transfer (send or receive). Cleans up the local file. |
| `apiCancelFile` | [L2080](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2080) | `(rh, fileId, ctrl?)` | Low-level cancel. Returns `AChatItem?` on success (`SndFileCancelled` or `RcvFileCancelled`). |
| `uploadStandaloneFile` | [L1916](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1916) | `(user, file, ctrl?)` | Upload a standalone file (for database migration). Returns `FileTransferMeta?` with XFTP link. |
| `downloadStandaloneFile` | [L1926](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L1926) | `(user, url, file, ctrl?)` | Download a standalone file from an XFTP URL. Returns `RcvFileTransfer?`. |
---
## 6. Auto-Receive Logic
Located in [`SimpleXAPI.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#L2696) within the `CR.NewChatItems` handler:
```kotlin
if (file != null &&
appPrefs.privacyAcceptImages.get() &&
((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV)
|| (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV)
|| (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV
&& file.fileStatus !is CIFileStatus.RcvAccepted))
) {
receiveFile(rhId, r.user, file.fileId, auto = true)
}
```
**Conditions for auto-receive:**
1. The `privacyAcceptImages` preference is enabled (user opt-in).
2. The content type and size match one of:
- **Images** (`MCImage`): file size <= 510 KB (`MAX_IMAGE_SIZE_AUTO_RCV`)
- **Video** (`MCVideo`): file size <= 1023 KB (`MAX_VIDEO_SIZE_AUTO_RCV`)
- **Voice** (`MCVoice`): file size <= 510 KB (`MAX_VOICE_SIZE_AUTO_RCV`) AND file is not already accepted
3. The file has a non-null `file` attachment.
When `auto = true`, relay approval alerts are suppressed (the file is silently received).
---
## 7. Source Files
| File | Path | Lines | Description |
|---|---|---|---|
| `CryptoFile.kt` | [`common/src/commonMain/.../model/CryptoFile.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/model/CryptoFile.kt) | 62 | Encrypted file read/write via native crypto |
| `Files.kt` | [`common/src/commonMain/.../platform/Files.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt) | 191 | Common file path declarations, theme I/O, file helpers |
| `Files.android.kt` | [`common/src/androidMain/.../platform/Files.android.kt`](../../common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt) | 79 | Android file path implementations |
| `Files.desktop.kt` | [`common/src/desktopMain/.../platform/Files.desktop.kt`](../../common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt) | 116 | Desktop file path implementations |
| `Utils.kt` | [`common/src/commonMain/.../views/helpers/Utils.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt) | -- | File size constants (L117--L128), `getMaxFileSize()` (L442) |
| `SimpleXAPI.kt` | [`common/src/commonMain/.../model/SimpleXAPI.kt`](../../common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt) | -- | File transfer API commands (L1911--L2085), auto-receive (L2690) |