Skip to content

Architecture

Architecture Overview

Overview

ECall iOS is an end-to-end encrypted (E2EE) audio and video calling application built using SwiftUI, WebRTC, and a modern Swift 6 architecture.

The system ensures full client-side media encryption using RSA-2048 for key exchange and AES-256-GCM for media encryption. All encryption and decryption happen locally on-device, and the backend cannot access media content.

High-Level Architecture

┌─────────────────────────────────────────────────────────┐
│                        App Layer                        │
│     App entry, root navigation, main tab navigation     │
└────────────────────┬────────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────────┐
│                      Modules Layer                      │
│     Authentication | Call | Contacts | Settings         │
└────────────────────┬────────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────────┐
│                        Core Layer                       │
│  Security | Networking | Persistence | Language | Utils │
└─────────────────────────────────────────────────────────┘

App Layer

Components

  • ECallApp.swift — Application entry point, built with SwiftUI.
  • RootView.swift — Determines the initial view based on the user's authentication state.
  • MainTabView.swift — Main tab navigation for Calls, Contacts, and Settings.
  • AppState.swift — Global state store for authentication status, tokens, and user information.

Responsibilities

  • Initialize global services.
  • Determine whether the user is authenticated.
  • Manage the navigation hierarchy.
  • Store application-wide reactive state.

Modules Layer

1. Authentication Module

Responsibilities

  • Provide a unified login and registration flow (“Continue” 2-in-1 sign-in/sign-up).
  • Handle OTP verification for email and phone.
  • Integrate with Google Sign-In and Sign in with Apple.
  • Manage RSA key pair generation and import.
  • Handle device registration and VoIP token setup.
  • Push public keys and device info to the backend.

Structure (code-aligned)

Path: ecall/Modules/Authentication

  • Models/
  • AuthResponse.swift
  • DeviceInfo.swift
  • VerifyResponse.swift
  • VoipRegisterResponse.swift
  • Services/
  • AuthService.swift
  • RSAKeyService.swift
  • UserService.swift
  • VoipService.swift
  • ViewModels/
  • AuthViewModel.swift
  • Views/
  • AuthFlowMainView.swift
  • AuthButton.swift
  • EmailAuthFormView.swift
  • PhoneAuthFormView.swift
  • OTPInputView.swift
  • OTPVerificationView.swift
  • GoogleSignInView.swift
  • KeysDisplayView.swift

Unified Authentication Flow (Continue 2-in-1)

At a high level, the Authentication module implements a “Continue” 2-in-1 flow: the user chooses a method (email, phone, Google, Apple), provides an identifier, and the backend decides whether this is a first-time registration or a login.

For email/phone, access is gated by OTP:

1. User selects a sign-in method (Email, Phone, Google, or Apple) in `AuthFlowMainView`.
2. For Email/Phone:
   2.1 User enters identifier + optional display name in `EmailAuthFormView`/`PhoneAuthFormView`.
   2.2 `AuthService.login(..., verified = false, ...)` is called.
   2.3 Backend sends OTP; response does NOT include accessToken or deviceId yet.
   2.4 UI transitions to `OTPVerificationView`.
   2.5 User enters OTP; `AuthService.verifyLogin(...)` is called.
   2.6 On success, backend returns `accessToken`, `deviceId`, and user info.
3. For Google/Apple:
   3.1 The app uses `GoogleSignInView` or the Apple sign-in flow.
   3.2 `AuthService.appleLogin(...)` or Google backend login is triggered.
   3.3 Backend returns `accessToken` and `deviceId` without OTP.
4. On authenticated success:
   4.1 RSA keys are generated/imported via `RSAKeyService`.
   4.2 Public key and device info are registered via `AuthService`/`UserService`/`VoipService`.
   4.3 `AppState` is updated with token and user info.
   4.4 Navigation moves to the main tab interface.

All login and verify requests attach device and crypto metadata (publicKey, voipToken, apnsToken, deviceName, systemName, systemVersion, identifier) so that the backend can register the device and E2EE keys.


2. Call Module

Responsibilities

  • Initiate outgoing calls.
  • Handle incoming calls using CallKit.
  • Manage WebRTC peer connections (publisher/subscriber).
  • Provide E2EE for all audio and video streams.
  • Use STOMP over WebSockets for signaling instead of SIP.
  • Integrate with the Janus Gateway for media routing.
  • Support rejoin functionality for active calls.

Structure (code-aligned)

Path: ecall/Modules/Call

  • Helpers/
  • CallUtils.swift
  • Managers/
  • GroupCallManager.swift
  • GroupCallSessionManager.swift
  • WebRTCManager.swift
  • CallEncryptionManager.swift
  • JanusSocketClient.swift
  • StompSignalingManager.swift
  • CallSignalingHandler.swift
  • CallKeyStorage.swift
  • ConnectionCoordinator.swift
  • SignalingDelegate.swift
  • PushRegistryManager.swift
  • AudioSessionManager.swift
  • SFXManager.swift
  • STOMPClient.swift
  • Services/
  • CallService.swift
  • CredentialsService.swift
  • Models/
  • CallModel.swift
  • CallSession.swift
  • ViewModels/
  • CallViewModel.swift
  • Views/
  • CallView.swift
  • CallHistory/CallHistoryView.swift and related row/detail views
  • SubViews/ (audio/video tiles, controls, layouts, key manager sheet)

The Call module is documented in detail in docs/technical/call-module.md. This architecture document focuses on how it fits in the overall system.


3. Contacts Module

Responsibilities

  • Add and search for contacts.
  • Manage friend requests.
  • Support adding friends via QR codes.
  • Display a contact list with metadata.

Structure (code-aligned)

Path: ecall/Modules/Contacts

  • Models/
  • ContactModel.swift
  • FriendRequest.swift (and related models)
  • Services/
  • ContactService.swift
  • FriendRequestService.swift (if present)
  • ViewModels/
  • ContactViewModel.swift
  • FriendRequestViewModel.swift (if present)
  • Views/
  • ContactsView.swift
  • AddFriendView.swift
  • QR-code related views for scanning and sharing contact QR codes

4. Settings Module

Responsibilities

  • Manage user profiles (name, email, phone, avatar).
  • Manage the list of active devices.
  • Allow language switching (15+ languages).
  • Manage login methods and account security.
  • Surface permission status (camera, microphone, notifications, local network) and deep-link into iOS Settings when denied.

Structure (code-aligned)

Path: ecall/Modules/Settings

  • Models/
  • DeviceModel.swift
  • PermissionModels.swift
  • Services/
  • DeviceService.swift
  • PermissionsService.swift
  • ViewModels/
  • DevicesViewModel.swift
  • PermissionsViewModel.swift
  • Views/
  • SettingsView.swift
  • DevicesView.swift
  • DeviceDetailsSheetView.swift
  • DeviceRowView.swift
  • LanguageSwitcherView.swift
  • PermissionsView.swift
  • Profile subviews:
    • MyProfileView.swift
    • ChangeNameView.swift
    • ChangeEmailView.swift
    • ChangePhoneNumberView.swift
    • LoginMethodsInfoView.swift

Core Layer

1. Security Module

Responsibilities

  • Generate and import RSA-2048 key pairs.
  • Encrypt audio and video with AES-256-GCM.
  • Encrypt and decrypt AES keys per device.
  • Provide a custom video encoder/decoder for encrypted WebRTC frames.
  • Provide a custom audio crypto delegate for encrypted audio packets.
  • Manage secure storage via the Keychain.

Key Components (code-aligned)

  • Core/Security/RSAEncryption.swift
  • Core/Security/CustomVideoEncoder.swift
  • Core/Security/CustomVideoEncoderFactory.swift
  • Core/Security/CustomVideoDecoder.swift
  • Core/Security/CustomVideoDecoderFactory.swift
  • Core/Security/H264NALUParser.swift
  • Core/Security/CustomAudioCrypto.swift
  • Core/Persistence/KeyStorage.swift

Encryption Flow

Caller:
  1. Generate a random AES-256 key.
  2. Encrypt the AES key using the callee’s RSA public key (OAEP-SHA256, with fallbacks).
  3. Send the encrypted AES key in the call invitation.
  4. Encrypt media with AES-256-GCM (video via custom encoder, audio via RTCAudioCryptoDelegate).

Callee:
  1. Receive the encrypted AES key.
  2. Decrypt it using the local RSA private key.
  3. Use the decrypted AES key to decrypt media (video and audio).

KeyStorage uses kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly for sensitive key material and never exposes private keys outside SecKey.


2. Networking Module

Components

  • Core/Networking/APIClient.swift — REST client with Bearer token authorization.
  • Core/Networking/APIEndpoint.swift — Endpoint definitions.
  • Core/Networking/Endpoints.swift — Environment configuration (Dev, Staging, Prod).
  • Modules/Call/Managers/StompSignalingManager.swift — WebSocket STOMP signaling for call events.
  • Modules/Call/Managers/JanusSocketClient.swift — WebSocket client for the Janus Gateway.
  • Core/Networking/NetworkMonitor.swift — Network connectivity monitoring.

Features

  • Custom JSON decoding with ISO8601 (with fallbacks).
  • Automatic token invalidation and logout on 401 Unauthorized.
  • WebSocket auto-reconnect behavior for signaling.
  • TURN/STUN support with fallback logic for poor networks.

3. Persistence Module

Responsibilities

  • Provide secure storage for keys, tokens, and sensitive user data via Keychain.
  • Store non-sensitive preferences in UserDefaults.
  • Cache TURN credentials for the Call module.

Components

  • Core/Persistence/KeyStorage.swift
  • Core/Persistence/CredentialStorage.swift

KeyStorage is responsible for storing: - User identifiers (userId, email, phoneNumber, displayName). - E2EE keys (public/private RSA). - Access tokens and deviceId. - VoIP tokens and APNs tokens.

CredentialStorage persists TURN credentials (TurnCredentials) used by CredentialsService.


4. Language Module

Responsibilities

  • Provide a localizable UI with support for 15+ languages.
  • Allow dynamic, runtime language switching.
  • Manage toast notifications.

Components

  • Core/Language/LanguageManager.swift
  • Core/Language/ToastManager.swift
  • Localized resources:
  • Resources/Localized/Localizable.xcstrings
  • Resources/Localized/KeyLocalized.swift

5. Utilities Module

Overview

Provides various helper utilities, including: - Date formatters (Core/Utilities/DateFormatters.swift). - App-wide helpers (Core/Utilities/AppUtils.swift). - Utility extensions for String, Color, View, and others (Core/Utilities/Extensions/). - Validation helpers for email and phone. - Permission helpers for camera, microphone, notifications, and local network (often consumed from Settings and Call modules).


State Management

Approaches

  • ObservableObject + @Published for reactive state in ViewModels.
  • @MainActor isolation for UI-facing classes and mutations.
  • NotificationCenter for cross-module events such as:
  • Call started/ended.
  • User profile updated.
  • Device list changed.

AppState acts as the primary source of truth for authentication and high-level application state.


Concurrency Model (Swift 6)

Techniques Used

  • async/await for asynchronous operations in networking and crypto where supported.
  • Structured concurrency via Task {} for background jobs.
  • @MainActor enforcement for UI updates.
  • WebRTC callbacks and encryption pipelines execute on dedicated RTC threads, bridged back to the main actor where necessary.

Performance Considerations

Highlights

  • Lazy loading of images and heavy views where possible.
  • Efficient WebRTC track and peer connection cleanup on call end.
  • TURN usage is limited to when STUN-only connectivity is not sufficient.
  • Hardware-accelerated encryption and codec usage where available (H264).

Security Considerations

Highlights

  • Full E2EE for all audio and video media using AES-256-GCM and RSA-2048.
  • Private keys stored in Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.
  • Private keys are never sent to the server; only public keys leave the device.
  • Recommended: certificate pinning and hardened TLS configuration for production builds.