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:
-
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.
-
processCallInvitation(encryptedAESKey:calleeRSAPrivateKey:):- Decrypts the AES key from an encrypted invitation.
- Sets up encryption for both video and audio.
-
encryptCallMediaData(_:): Encrypts media data using AES-GCM. -
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¶
- Never log sensitive data: This includes private keys, tokens, and passwords.
- Use the Keychain: All sensitive data should be stored in the Keychain.
- Secure Deletion: Clear keys from memory after use.
- Certificate Validation: Validate server certificates.
Networking¶
- Error Handling: Always handle network errors gracefully.
- Retry Logic: Implement retry logic for critical operations.
- Timeouts: Set appropriate timeouts for all requests.
- Certificate Pinning: Consider using certificate pinning in production.
Persistence¶
- Keychain Only: Sensitive data should only be stored in the Keychain.
- UserDefaults: Only use for non-sensitive user preferences.
- Data Cleanup: Clear all data on logout.
Language¶
- Bundle Caching: Cache language bundles for better performance.
- Fallback: Always have a fallback language.
- Testing: Test with all supported languages.