Skip to the content.

RFC 0003: iOS File Provider Extension

Status: Draft Author: xoxd Date: 2026-02-22 Tracking: Phase 7 (Future)


Abstract

This RFC describes the architecture for an iOS File Provider extension that exposes tcfs storage in the iOS Files app. The extension reuses existing Rust crates (tcfs-storage, tcfs-chunks, tcfs-crypto, tcfs-sync) via Mozilla UniFFI, bridging to Swift for the native FileProviderExtension API.

Motivation

iOS is a first-class target for tcfs. Users should be able to:

The iOS File Provider framework provides the system integration point, similar to how tcfs-fuse provides Linux integration and tcfs-cloudfilter provides Windows integration.

Architecture

┌──────────────────────────────────────────────────┐
│                    iOS Files App                  │
├──────────────────────────────────────────────────┤
│           NSFileProviderExtension                │
│  ┌────────────────────────────────────────────┐  │
│  │              Swift Layer                    │  │
│  │  - FileProviderExtension.swift             │  │
│  │  - FileProviderItem.swift                  │  │
│  │  - FileProviderEnumerator.swift            │  │
│  │  - ContentKeychain.swift (~2000 LOC)       │  │
│  └────────────────┬───────────────────────────┘  │
│                   │ UniFFI (C ABI)               │
│  ┌────────────────┴───────────────────────────┐  │
│  │          tcfs-file-provider (Rust)          │  │
│  │  - uniffi bindings (~1000 LOC)             │  │
│  │  - async task bridge                       │  │
│  │  - credential adapter (Keychain)           │  │
│  └────────────────┬───────────────────────────┘  │
│                   │                              │
│  ┌────────────────┴───────────────────────────┐  │
│  │           Reused tcfs Crates               │  │
│  │  ┌─────────────┐  ┌──────────────┐        │  │
│  │  │ tcfs-storage │  │  tcfs-chunks │        │  │
│  │  │ (70% reuse)  │  │ (100% reuse) │        │  │
│  │  └──────────────┘  └──────────────┘        │  │
│  │  ┌─────────────┐  ┌──────────────┐        │  │
│  │  │  tcfs-sync  │  │  tcfs-crypto │        │  │
│  │  │ (80% reuse) │  │ (100% reuse) │        │  │
│  │  └──────────────┘  └──────────────┘        │  │
│  └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘

Reusable Code

Crate Reuse % Notes
tcfs-chunks 100% Pure computation, no platform deps
tcfs-crypto 100% Pure computation, no platform deps
tcfs-storage 70% OpenDAL S3 works on iOS; health checks need adaptation
tcfs-sync 80% State cache and vector clocks reusable; NATS client needs iOS networking adaptation
tcfs-core 100% Proto types, config structs

New Code

Component LOC (est.) Language
UniFFI bindings ~1000 Rust
Swift FileProviderExtension ~2000 Swift
Xcode project / build scripts ~500 Various

Hydration Pattern

The hydration flow mirrors tcfs-fuse and tcfs-cloudfilter:

User taps file in Files app
       │
       ▼
NSFileProviderExtension.startProvidingItem(at:completionHandler:)
       │
       ▼
tcfs_file_provider::hydrate(item_id)
       │
       ├── 1. Fetch manifest from S3: manifests/{file_hash}
       ├── 2. Parse SyncManifest v2 (JSON)
       ├── 3. Fetch chunks in parallel: chunks/{chunk_hash}
       ├── 4. Decrypt chunks (XChaCha20-Poly1305)
       ├── 5. Decompress chunks (zstd)
       └── 6. Reassemble and write to provided URL
       │
       ▼
completionHandler(nil)  // success

Platform Analogs

Concept Linux (tcfs-fuse) Windows (tcfs-cloudfilter) iOS (tcfs-file-provider)
Integration point FUSE kernel module Cloud Files minifilter NSFileProviderExtension
Stub/placeholder .tc file CFAPI placeholder NSFileProviderItem
Hydration trigger read() syscall CF_CALLBACK_FETCH_DATA startProvidingItem()
Dehydration unsync command CfDehydratePlaceholder itemChanged(at:)
Directory listing readdir() CfGetPlaceholders enumerator(for:)

UniFFI Interface Definition

namespace tcfs_file_provider {
    // Initialize the provider with S3 credentials
    [Throws=ProviderError]
    void initialize(ProviderConfig config);

    // List files at a given path
    [Throws=ProviderError]
    sequence<FileItem> list_items(string path);

    // Hydrate a file (download + decrypt + decompress)
    [Throws=ProviderError]
    void hydrate_file(string item_id, string destination_path);

    // Upload a local file
    [Throws=ProviderError]
    void upload_file(string local_path, string remote_path);

    // Get sync status
    [Throws=ProviderError]
    SyncStatus get_sync_status();
};

dictionary ProviderConfig {
    string s3_endpoint;
    string s3_bucket;
    string access_key;
    string secret_key;
    string remote_prefix;
    string? encryption_key;
};

dictionary FileItem {
    string item_id;
    string filename;
    u64 file_size;
    i64 modified_timestamp;
    boolean is_directory;
    string content_hash;
};

dictionary SyncStatus {
    boolean connected;
    u64 files_synced;
    u64 files_pending;
    string? last_error;
};

[Error]
enum ProviderError {
    "StorageError",
    "DecryptionError",
    "NetworkError",
    "NotFound",
    "PermissionDenied",
};

Phase Roadmap

Phase 7a: Skeleton (This Sprint)

Phase 7b: Basic Hydration

Phase 7c: E2E Encryption

Phase 7d: Sync Engine

Phase 7e: UI + Polish

Technical Challenges

Async FFI

UniFFI supports async functions but the bridge between tokio (Rust) and Swift concurrency (async/await) requires careful lifetime management. The recommended pattern is to run a tokio runtime in the Rust layer and expose blocking or callback-based APIs to Swift.

// Rust side: run async in dedicated runtime
static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());

#[uniffi::export]
fn hydrate_file(item_id: String, dest: String) -> Result<(), ProviderError> {
    RUNTIME.block_on(async {
        // ... async hydration logic
    })
}

Keychain Credentials

iOS sandbox prevents reading env vars or config files from the host. Credentials must be stored in the iOS Keychain and accessed via Security.framework:

let query: [String: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "com.tummycrypt.tcfsd",
    kSecAttrAccount: "s3_access_key",
    kSecReturnData: true,
]

App Sandbox Restrictions

OpenDAL iOS Compilation

OpenDAL with services-s3 feature needs:

Dependency Chain

tcfs-core (proto types, config)
    │
    ├── tcfs-storage (OpenDAL S3 operator)
    │       │
    │       └── tcfs-chunks (FastCDC + BLAKE3 + zstd)
    │               │
    │               └── tcfs-crypto (XChaCha20 + Argon2id)
    │
    └── tcfs-sync (state cache, vector clocks)
            │
            └── tcfs-file-provider (UniFFI bridge) ← NEW
                    │
                    └── Swift FileProviderExtension (Xcode project) ← FUTURE

References


Signed-off-by: xoxd