Skip to content

Core Modules Deep Dive

This document provides a detailed overview of the core modules in the ECall iOS application.

Security Module

Overview

The Security module manages all encryption and decryption operations, primarily for end-to-end encrypted (E2EE) calls.

Key Components

CallEncryptionManager

File: ecall/Modules/Call/Managers/CallEncryptionManager.swift

Responsibilities: - Generates and manages AES session keys. - Encrypts and decrypts AES keys using RSA public/private keys. - Encrypts and decrypts call media data (audio and video). - Sets up audio and video encryption delegates.

Key Properties:

var sessionAESKey: Data?        // The AES key for the current call
var originalAESKey: Data?       // The original AES key (before any simulation)
var cryptoDelegate: CustomAudioCrypto?  // The audio encryption delegate

Key Methods:

  1. prepareCallInvitation(with:):

    • Generates a new AES key.
    • Encrypts the AES key with the callee's public key.
    • Sets up encryption for both video and audio.
  2. processCallInvitation(encryptedAESKey:calleeRSAPrivateKey:):

    • Decrypts the AES key from an encrypted invitation.
    • Sets up encryption for both video and audio.
  3. encryptCallMediaData(_:): Encrypts media data using AES-GCM.

  4. decryptCallMediaData(_:): Decrypts media data using AES-GCM.

Encryption Flow:

Caller Side:
1. Generate a random AES-256 key.
2. Encrypt the AES key with the callee's RSA public key (OAEP-SHA256).
3. Send the encrypted AES key in the call invitation.
4. Use the AES key to encrypt media data.

Callee Side:
1. Receive the encrypted AES key.
2. Decrypt it with the local RSA private key (attempts multiple algorithms).
3. Use the decrypted AES key to decrypt media data.

RSA Encryption

File: ecall/Core/Security/RSAEncryption.swift

Functions: - generateAESKey(): Generates a 256-bit random AES key. - encryptAESKey(aesKey:with:): Encrypts an AES key with an RSA public key. - decryptAESKey(encryptedAESKey:with:): Decrypts an AES key with an RSA private key. - encryptCallData(_:using:): Encrypts data with AES-GCM. - decryptCallData(_:using:): Decrypts data with AES-GCM.

Algorithm Support: - RSA: OAEP-SHA256 (primary), with fallbacks to OAEP-SHA1 and PKCS1. - AES: 256-bit GCM mode.

Custom Video Encoder/Decoder

Files: - CustomVideoEncoder.swift - CustomVideoEncoderFactory.swift - CustomVideoDecoder.swift - CustomVideoDecoderFactory.swift - H264NALUParser.swift

Purpose: To integrate E2EE into the WebRTC video pipeline by: 1. Intercepting video frames from the camera. 2. Encrypting the video frames with AES-GCM. 3. Decrypting the video frames before rendering.

Flow:

Camera → H264 Encoder → NALU Parser → AES Encrypt → WebRTC → Network
Network → WebRTC → AES Decrypt → NALU Parser → H264 Decoder → Render

Custom Audio Crypto

File: CustomAudioCrypto.swift

Purpose: To implement the RTCAudioCryptoDelegate protocol to encrypt and decrypt audio packets.

Implementation:

class CustomAudioCrypto: NSObject, RTCAudioCryptoDelegate {
    private let key: SymmetricKey

    func encrypt(_ sampleBuffer: CMSampleBuffer) -> CMSampleBuffer? {
        // Encrypt audio samples
    }

    func decrypt(_ sampleBuffer: CMSampleBuffer) -> CMSampleBuffer? {
        // Decrypt audio samples
    }
}

Key Storage

File: ecall/Core/Persistence/KeyStorage.swift

Purpose: To provide secure storage in the iOS Keychain.

Stored Items: - Private and Public RSA keys - Access tokens - User info (userId, email, displayName) - Device info (deviceId, deviceName, systemName) - VoIP token

Keychain Access Policy: - kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: Keys are only accessible after the device has been unlocked once.

Key Methods: - storeUserInfosInKeychain(...): Stores user information and keys. - readPrivateKeyAsSecKey(): Reads the private key as a SecKey object. - readPublicKey(): Reads the public key as a string. - removeUserInfos(): Clears all user data from the Keychain.

Security Features: - Robust base64 decoding with error handling. - Stripping of PEM headers and footers. - Support for URL-safe base64. - Padding handling.

Networking Module

Overview

The Networking module manages all network communications, including REST API calls, WebSocket signaling, and communication with the Janus Gateway.

Key Components

APIClient

File: ecall/Core/Networking/APIClient.swift

Pattern: A singleton with a generic request method.

Features: - Bearer token authentication. - Automatic handling of 401 Unauthorized errors (triggers logout). - A custom JSON decoder with flexible date parsing. - Error handling using the APIError enum.

Request Flow:

Request → Create URLRequest → Add Bearer Token → URLSession → 
Handle Response → Decode JSON → Return Result<T, APIError>

Error Types:

enum APIError: Error {
    case invalidData
    case invalidURL
    case responseError(retCode: Int, message: String)
    case noData
    case decodingError(Error)
    case statusCode(Int)
    case userNotFound
    case serverError
    case unauthorized
}

Date Decoding Strategy: - ISO8601 with fractional seconds. - Standard ISO8601. - Fallback to "yyyy-MM-dd HH:mm:ss".

API Endpoints

File: ecall/Core/Networking/APIEndpoint.swift

Structure: An enum with path definitions and URL builders.

Key Endpoints (selected): - WebSocket / Janus: - .wss/ws/ - .janus/janus - Authentication (public app endpoints): - .verifyUser/app/verify - .resendOTP/app/resend-otp - .login/app/login - .verifyLogin/app/verify-login - .appleLogin/app/apple-login - Auth session (authenticated): - .logout/app/api/logout - .terminateSession(deviceId)/app/api/terminate-session/{deviceId} - .terminateOthers/app/api/terminate-others - Contacts & Friends: - .contact / .contacts/app/api/contact, /app/api/contacts - .friendRequest + derived actions (accept, cancel, decline) - .friendRequestSent, .friendRequestReceived - Calls: - .calls, .callHistories - .startCall, .activeCall - .inviteToCall(id), .joinCall(id), .rejoinCall(id), .requestRejoinCall(id) - .participants(id) - TURN Credentials: - .credentials - User & Keys: - .registerDevice - .currentUser, .updateUser - .publicKey, .publicKeys

URL Building helpers:

var fullURLString: String {
    return Endpoints.shared.baseURL + path
}

var fullURL: URL { ... }
var fullSocketURL: URL { ... }
var fullJanusSocketURL: URL { ... }

Environment Configuration

File: ecall/Core/Networking/Endpoints.swift

Environments: - Dev - Staging - Production

Configuration: - Read from Info.plist (via Build Settings). - ENVIRONMENT_NAME: The name of the environment. - API_BASE_URL: The base URL for the REST API. - SOCKET_BASE_URL: The base URL for the WebSocket. - JANUS_SOCKET_URL: The URL for the Janus Gateway.

Usage:

let environment = AppEnvironment.current
environment.printInfo()  // Logs environment information

STOMP Signaling Manager

File: ecall/Modules/Call/Managers/StompSignalingManager.swift

Purpose: To handle WebSocket-based signaling using the STOMP protocol.

Features: - Auto-reconnect functionality. - Connection state management. - Message routing. - Handling of call invitations.

Message Types: - call_invitation: An incoming call invitation. - call_accept: A notification that a call was accepted. - call_reject: A notification that a call was rejected. - call_end: A notification that a call has ended.

Janus Socket Client

File: ecall/Modules/Call/Managers/JanusSocketClient.swift

Purpose: A WebSocket client for communicating with the Janus Gateway.

Features: - Session management. - Plugin attachment (for the video room). - Room creation and joining. - WebRTC offer/answer exchange. - ICE candidate exchange.

Janus Flow:

1. Create Session → Get session_id
2. Attach Plugin → Get handle_id
3. Create/Join Room → Get room_id
4. Exchange WebRTC SDP (offer/answer)
5. Exchange ICE candidates
6. Establish WebRTC connection

Network Monitoring

File: ecall/Core/Networking/NetworkMonitor.swift

Purpose: To monitor network connectivity.

Features: - Network path monitoring. - Detection of connection type (Wi-Fi, Cellular). - Callbacks for connection state changes.

Persistence Module

Overview

The Persistence module manages local data storage, using the Keychain for sensitive data and UserDefaults for user preferences.

Key Components

KeyStorage

File: ecall/Core/Persistence/KeyStorage.swift

Purpose: A wrapper for Keychain operations.

Key Methods: - storeUserInfosInKeychain(...): Stores user information. - storeVoipToken(voipToken:): Stores the VoIP push token. - saveDeviceId(_:): Stores the device ID. - readUserId(): Reads the user ID. - readPrivateKeyAsSecKey(): Reads the private key as a SecKey object. - readAccessToken(): Reads the access token. - removeUserInfos(): Clears all user data.

Keychain Items: - org.ecall.userId - org.ecall.email - org.ecall.phoneNumber - org.ecall.displayName - org.ecall.publicKey - org.ecall.privateKey - org.ecall.accessToken - org.ecall.voipToken - org.ecall.deviceId

Security: - All items are stored with the kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly protection level. - Private keys are never exposed as plain strings (only as SecKey objects).

Credential Storage

File: ecall/Core/Persistence/CredentialStorage.swift

Purpose: To store TURN server credentials.

Data Structure:

struct TurnCredentials {
    let turnUsername: String
    let turnPassword: String
    let noneTlsUrl: String
    let tlsUrl: String?
}

Language Module

Overview

The Language module manages multi-language support for over 15 languages.

Supported Languages: Vietnamese, English, German, Spanish, French, Hindi, Italian, Japanese, Khmer, Korean, Portuguese, Russian, Thai, and Chinese.

Key Components

Language Manager

File: ecall/Core/Language/LanguageManager.swift

Features: - Dynamic language switching. - Bundle caching for improved performance. - Locale support for SwiftUI. - Uses @AppStorage to persist the user's language preference.

Usage:

// Get a localized string
let text = LanguageManager.shared.localizedString("key_name")

// Use the String extension
let text = "key_name".localized()

// Use with arguments
let text = "key_name".localized(with: "arg1", "arg2")

Localization Files: - Localizable.xcstrings: The main localization file. - KeyLocalized.swift: Contains key constants.

Toast Manager

File: ecall/Core/Language/ToastManager.swift

Purpose: A toast notification system that uses an overlay window.

Features: - Toast queue management. - Auto-dismiss functionality. - Multiple toast types (success, error, info). - A focus mode with a background scrim. - Touch passthrough for background views.

Usage:

ToastManager.shared.success("Message")
ToastManager.shared.error("Error message")
ToastManager.shared.info("Info message")

Architecture: - PassthroughWindow: A custom window to handle touch passthrough. - ToastsContainer: A SwiftUI container for toasts. - ToastView: An individual toast view with auto-dismiss.

Utilities Module

Overview

The Utilities module contains various helper classes and extensions.

Key Components

Date Formatters

File: ecall/Core/Utilities/DateFormatters.swift

Predefined Formatters: - iso8601Fractional: ISO8601 with fractional seconds. - iso8601: Standard ISO8601. - dateShort: A short date format. - timeShort: A short time format.

App Utils

File: ecall/Core/Utilities/AppUtils.swift

Helper Functions: - URL validation. - Phone number formatting. - Email validation.

Extensions

Path: ecall/Core/Utilities/Extensions/

Key Extensions: - String+Extension.swift: String utilities. - Color+Extension.swift: Color utilities. - View+Extension.swift: View modifiers. - Other+Extension.swift: Other miscellaneous utilities.

Best Practices

Security

  1. Never log sensitive data: This includes private keys, tokens, and passwords.
  2. Use the Keychain: All sensitive data should be stored in the Keychain.
  3. Secure Deletion: Clear keys from memory after use.
  4. Certificate Validation: Validate server certificates.

Networking

  1. Error Handling: Always handle network errors gracefully.
  2. Retry Logic: Implement retry logic for critical operations.
  3. Timeouts: Set appropriate timeouts for all requests.
  4. Certificate Pinning: Consider using certificate pinning in production.

Persistence

  1. Keychain Only: Sensitive data should only be stored in the Keychain.
  2. UserDefaults: Only use for non-sensitive user preferences.
  3. Data Cleanup: Clear all data on logout.

Language

  1. Bundle Caching: Cache language bundles for better performance.
  2. Fallback: Always have a fallback language.
  3. Testing: Test with all supported languages.