TextSecureKit Refactoring

- Using same clang format file for old and new files.
- Moving out all TextSecure code to allow other clients (OS X, iOS) to
  integrate easily TextSecure functionality.
- Use TextSecure API to signup.
This commit is contained in:
Frederic Jacobs 2015-12-22 12:45:09 +01:00
parent 37b582beda
commit c6d44e59e2
473 changed files with 10469 additions and 22905 deletions

View File

@ -1,7 +1,11 @@
---
BasedOnStyle: Chromium
AlignTrailingComments: true
BreakBeforeBraces: Linux
AlignConsecutiveAssignments: true
AllowShortIfStatementsOnASingleLine: false
BreakBeforeBraces: Attach
BinPackArguments: false
BinPackParameters: false
ColumnLimit: 120
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false

4
.gitignore vendored
View File

@ -25,4 +25,6 @@ xcuserdata
DerivedData
*.hmap
*.ipa
*.xcuserstate
*.xcuserstate
Pods/

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "Pods"]
path = Pods
url = https://github.com/FredericJacobs/Precompiled-Signal-Dependencies.git

View File

@ -6,7 +6,7 @@ xcode_sdk: iphonesimulator9.1
before_install:
- brew update
- brew uninstall xctool && brew install --HEAD xctool
install: true
install: travis_wait pod install
xcode_workspace: Signal.xcworkspace
xcode_scheme: Signal

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

12
Podfile
View File

@ -1,21 +1,13 @@
platform :ios, '8.0'
source 'https://github.com/CocoaPods/Specs.git'
inhibit_all_warnings!
link_with ["Signal", "SignalTests"]
pod 'OpenSSL', '~> 1.0.204.1'
pod 'libPhoneNumber-iOS', '~> 0.8.7'
pod 'AxolotlKit'
pod 'TextSecureKit', :git => 'https://github.com/WhisperSystems/TextSecureKit', :branch => 'master'
pod 'OpenSSL', '~> 1.0.205'
pod 'PastelogKit', '~> 1.3'
pod 'TwistedOakCollapsingFutures','~> 1.0'
pod 'AFNetworking', '~> 2.6'
pod 'Mantle', '~> 2.0.4'
pod 'FFCircularProgressView', '~> 0.5'
pod 'SCWaveformView', '~> 1.0'
pod 'YapDatabase/SQLCipher', '~> 2.7.2'
pod 'iRate', '~> 1.11'
pod 'SSKeychain'
pod 'DJWActionSheet'
pod 'SocketRocket', :git => 'https://github.com/FredericJacobs/SocketRocket.git', :commit => 'dd7e47fed78214785d7c032081da94008335fc35'
pod 'JSQMessagesViewController', :git => 'https://github.com/WhisperSystems/JSQMessagesViewController', :commit => 'e5582fef8a6b3e35f8070361ef37237222da712b'

View File

@ -1,27 +1,21 @@
PODS:
- 25519 (2.0.2)
- AFNetworking (2.6.3):
- AFNetworking/NSURLConnection (= 2.6.3)
- AFNetworking/NSURLSession (= 2.6.3)
- AFNetworking/Reachability (= 2.6.3)
- AFNetworking/Security (= 2.6.3)
- AFNetworking/Serialization (= 2.6.3)
- AFNetworking/UIKit (= 2.6.3)
- AFNetworking/NSURLConnection (2.6.3):
- AFNetworking (3.0.4):
- AFNetworking/NSURLSession (= 3.0.4)
- AFNetworking/Reachability (= 3.0.4)
- AFNetworking/Security (= 3.0.4)
- AFNetworking/Serialization (= 3.0.4)
- AFNetworking/UIKit (= 3.0.4)
- AFNetworking/NSURLSession (3.0.4):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/NSURLSession (2.6.3):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/Reachability (2.6.3)
- AFNetworking/Security (2.6.3)
- AFNetworking/Serialization (2.6.3)
- AFNetworking/UIKit (2.6.3):
- AFNetworking/NSURLConnection
- AFNetworking/Reachability (3.0.4)
- AFNetworking/Security (3.0.4)
- AFNetworking/Serialization (3.0.4)
- AFNetworking/UIKit (3.0.4):
- AFNetworking/NSURLSession
- AxolotlKit (0.7):
- AxolotlKit (0.8):
- 25519 (~> 2.0.1)
- HKDFKit (~> 0.0.3)
- ProtocolBuffers (~> 1.9.8)
@ -41,65 +35,69 @@ PODS:
- JSQSystemSoundPlayer (~> 2.0.1)
- JSQSystemSoundPlayer (2.0.1)
- libPhoneNumber-iOS (0.8.10)
- Mantle (2.0.5):
- Mantle/extobjc (= 2.0.5)
- Mantle/extobjc (2.0.5)
- OpenSSL (1.0.204.1)
- Mantle (2.0.6):
- Mantle/extobjc (= 2.0.6)
- Mantle/extobjc (2.0.6)
- OpenSSL (1.0.205)
- PastelogKit (1.3):
- CocoaLumberjack (~> 2.0)
- ProtocolBuffers (1.9.9.2)
- SCWaveformView (1.0.0)
- SocketRocket (0.4.2)
- SQLCipher/common (3.1.0)
- SQLCipher/fts (3.1.0):
- SocketRocket-PinningPolicy (0.4.3)
- SQLCipher/common (3.3.1)
- SQLCipher/fts (3.3.1):
- SQLCipher/common
- SSKeychain (1.2.3)
- SSKeychain (1.3.1)
- TextSecureKit (0.0.4):
- '25519'
- AFNetworking
- AxolotlKit
- CocoaLumberjack
- libPhoneNumber-iOS
- Mantle
- SocketRocket-PinningPolicy
- SSKeychain
- TwistedOakCollapsingFutures
- YapDatabase/SQLCipher
- TwistedOakCollapsingFutures (1.0.0):
- UnionFind (~> 1.0)
- UnionFind (1.0.1)
- YapDatabase/SQLCipher (2.7.6):
- YapDatabase/SQLCipher (2.7.7):
- CocoaLumberjack (~> 2)
- SQLCipher/fts
DEPENDENCIES:
- AFNetworking (~> 2.6)
- AxolotlKit
- DJWActionSheet
- FFCircularProgressView (~> 0.5)
- iRate (~> 1.11)
- JSQMessagesViewController (from `https://github.com/WhisperSystems/JSQMessagesViewController`,
commit `e5582fef8a6b3e35f8070361ef37237222da712b`)
- libPhoneNumber-iOS (~> 0.8.7)
- Mantle (~> 2.0.4)
- OpenSSL (~> 1.0.204.1)
- OpenSSL (~> 1.0.205)
- PastelogKit (~> 1.3)
- SCWaveformView (~> 1.0)
- SocketRocket (from `https://github.com/FredericJacobs/SocketRocket.git`, commit
`dd7e47fed78214785d7c032081da94008335fc35`)
- SSKeychain
- TwistedOakCollapsingFutures (~> 1.0)
- YapDatabase/SQLCipher (~> 2.7.2)
- TextSecureKit (from `https://github.com/WhisperSystems/TextSecureKit`, branch
`master`)
EXTERNAL SOURCES:
JSQMessagesViewController:
:commit: e5582fef8a6b3e35f8070361ef37237222da712b
:git: https://github.com/WhisperSystems/JSQMessagesViewController
SocketRocket:
:commit: dd7e47fed78214785d7c032081da94008335fc35
:git: https://github.com/FredericJacobs/SocketRocket.git
TextSecureKit:
:branch: master
:git: https://github.com/WhisperSystems/TextSecureKit
CHECKOUT OPTIONS:
JSQMessagesViewController:
:commit: e5582fef8a6b3e35f8070361ef37237222da712b
:git: https://github.com/WhisperSystems/JSQMessagesViewController
SocketRocket:
:commit: dd7e47fed78214785d7c032081da94008335fc35
:git: https://github.com/FredericJacobs/SocketRocket.git
TextSecureKit:
:commit: 8d6ce0b57c2d7de63d4e22f205ea87622432088e
:git: https://github.com/WhisperSystems/TextSecureKit
SPEC CHECKSUMS:
'25519': dc4bad7e2dbcbf1efa121068a705a44cd98c80fc
AFNetworking: cb8d14a848e831097108418f5d49217339d4eb60
AxolotlKit: 8652fca51f4bc8225cbda791b0026c21e912b694
AFNetworking: a0075feb321559dc78d9d85b55d11caa19eabb93
AxolotlKit: a33962f26943990e5d69d05b30470cea18caeed0
CocoaLumberjack: 17fe8581f84914d5d7e6360f7c70022b173c3ae0
DJWActionSheet: 2fe54b1298a7f0fe44462233752c76a530e0cd80
FFCircularProgressView: 683a4ab1e1bd613246a3dffa61503ffdebcde8d8
@ -108,16 +106,17 @@ SPEC CHECKSUMS:
JSQMessagesViewController: ca11f86fa68ca70835f05e169df9244147c1dc40
JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d
libPhoneNumber-iOS: 7bfd00f843fdcd82b5182b463e8eb3b27579f41d
Mantle: 1912395033f601de5adc8ee91e48f46e4c7051ad
OpenSSL: 7f853fcada78e5162c2183b4d90ebbd0aa02f5ac
Mantle: 299966b00759634931699f69cb6a30b9239b944d
OpenSSL: 162687d7e96a3edeb4cf443ca6ce7cdb2912df7b
PastelogKit: 7b475be4cf577713506a943dd940bcc0499c8bca
ProtocolBuffers: 7111461618460961e6b7469177ec45ee551b4f0e
SCWaveformView: 52a96750255d817e300565a80c81fb643e233e07
SocketRocket: ffe08119b00ef982f6c37052a4705a057c8494ad
SQLCipher: c44fcca49c31483c2489cfb79aa7a69407f3794a
SSKeychain: 3f42991739c6c60a9cf1bbd4dff6c0d3694bcf3d
SocketRocket-PinningPolicy: f2ef00c3927bac05cd04d9d5171f82d922b40d3d
SQLCipher: d490d2effe735895d2204d25a154b1e722278421
SSKeychain: 04c4ec40f4c3deda524e269bc76e5f30e638a174
TextSecureKit: b7419cbc792e5be4cbfd93a4da340f7a51d7bc41
TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c
UnionFind: c33be5adb12983981d6e827ea94fc7f9e370f52d
YapDatabase: 00e5a5d1b5dba1bd540ef644576233b5612e6122
YapDatabase: a7a1ae3e0f89c319e3b22615c2351987fbbdbded
COCOAPODS: 0.39.0

1
Pods

@ -1 +0,0 @@
Subproject commit e6c803ff3cdb391d5c0dc9a2418f1e2f0797a1a3

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,8 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
allowLocationSimulation = "YES"
showNonLocalizedStrings = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference

View File

@ -5,26 +5,26 @@
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C" : 0,
"D74FB800F048CB516BB4BC70047F7CC676D291B9" : 0
"37054CE35CE656680D6FFFA9EE19249E0D149C5E" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1D917339-756B-4BA2-8BAF-70AFEDF98BC0",
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "D0F297E7-A82D-4657-A941-96B268F80ABC",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C" : "Signal-iOS",
"D74FB800F048CB516BB4BC70047F7CC676D291B9" : "Signal-iOSPods"
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C" : "Signal-iOS\/",
"37054CE35CE656680D6FFFA9EE19249E0D149C5E" : "TSKit\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "Signal",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Signal.xcworkspace",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:FredericJacobs\/TextSecureKit.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:FredericJacobs\/Precompiled-Signal-Dependencies.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D74FB800F048CB516BB4BC70047F7CC676D291B9"
}
]
}

Binary file not shown.

View File

@ -38,7 +38,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2.3.0</string>
<string>2.3.0.1</string>
<key>LOGS_EMAIL</key>
<string>support@whispersystems.org</string>
<key>LOGS_URL</key>

View File

@ -19,7 +19,4 @@
#define SignalAlertView(title,msg) [[[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil, nil] show]
#define SignalReportError [Pastelog reportErrorAndSubmitLogsWithAlertTitle:NSLocalizedString(@"ERROR_WAS_DETECTED_TITLE", @"") alertBody:NSLocalizedString(@"ERROR_WAS_DETECTED_SUBMIT", @"")];
#define BLOCK_SAFE_RUN(block, ...) block ? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{block(__VA_ARGS__);}) : nil
#define SYNC_BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__): nil
#endif

View File

@ -2,7 +2,7 @@
#import "SignalsViewController.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) SignalsViewController *signalVC;

View File

@ -1,23 +1,25 @@
#import "AppDelegate.h"
#import "AppStoreRating.h"
#import "CategorizingLogger.h"
#import "CodeVerificationViewController.h"
#import "ContactsManager.h"
#import "DebugLogger.h"
#import "Environment.h"
#import "NotificationsManager.h"
#import "PreferencesUtil.h"
#import "PushManager.h"
#import "Release.h"
#import "TSAccountManager.h"
#import "TSPreKeyManager.h"
#import "TSMessagesManager.h"
#import "TSPreKeyManager.h"
#import "TSSocketManager.h"
#import "TextSecureKitEnv.h"
#import "VersionMigrations.h"
#import "CodeVerificationViewController.h"
static NSString * const kStoryboardName = @"Storyboard";
static NSString * const kInitialViewControllerIdentifier = @"UserInitialViewController";
static NSString * const kURLSchemeSGNLKey = @"sgnl";
static NSString * const kURLHostVerifyPrefix = @"verify";
static NSString *const kStoryboardName = @"Storyboard";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
static NSString *const kURLSchemeSGNLKey = @"sgnl";
static NSString *const kURLHostVerifyPrefix = @"verify";
@interface AppDelegate ()
@ -29,7 +31,7 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
#pragma mark Detect updates - perform migrations
+ (void)initialize{
+ (void)initialize {
[AppStoreRating setupRatingLibrary];
}
@ -40,58 +42,63 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setupAppearance];
[[PushManager sharedManager] registerPushKitNotificationFuture];
if (getenv("runningTests_dontStartApp")) {
return YES;
}
CategorizingLogger* logger = [CategorizingLogger categorizingLogger];
[logger addLoggingCallback:^(NSString *category, id details, NSUInteger index) {}];
// Initializing logger
CategorizingLogger *logger = [CategorizingLogger categorizingLogger];
[logger addLoggingCallback:^(NSString *category, id details, NSUInteger index){
}];
// Setting up environment
[Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]];
if ([TSAccountManager isRegistered]) {
[Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup];
}
[Environment.getCurrent initCallListener];
[[TSStorageManager sharedManager] setupDatabase];
[self setupTSKitEnv];
BOOL loggingIsEnabled;
#ifdef DEBUG
// Specified at Product -> Scheme -> Edit Scheme -> Test -> Arguments -> Environment to avoid things like
// the phone directory being looked up during tests.
loggingIsEnabled = TRUE;
[DebugLogger.sharedInstance enableTTYLogging];
[DebugLogger.sharedLogger enableTTYLogging];
#elif RELEASE
loggingIsEnabled = Environment.preferences.loggingIsEnabled;
#endif
[self verifyBackgroundBeforeKeysAvailableLaunch];
if (loggingIsEnabled) {
[DebugLogger.sharedInstance enableFileLogging];
[DebugLogger.sharedLogger enableFileLogging];
}
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:kStoryboardName bundle:[NSBundle mainBundle]];
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:kInitialViewControllerIdentifier];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *viewController =
[storyboard instantiateViewControllerWithIdentifier:kInitialViewControllerIdentifier];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
[VersionMigrations performUpdateCheck]; // this call must be made after environment has been initialized because in general upgrade may depend on environment
//Accept push notification when app is not open
[VersionMigrations performUpdateCheck]; // this call must be made after environment has been initialized because in
// general upgrade may depend on environment
// Accept push notification when app is not open
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotif) {
DDLogInfo(@"Application was launched by tapping a push notification.");
[self application:application didReceiveRemoteNotification:remoteNotif ];
[self application:application didReceiveRemoteNotification:remoteNotif];
}
[self prepareScreenshotProtection];
if ([TSAccountManager isRegistered]) {
if (application.applicationState == UIApplicationStateInactive) {
[TSSocketManager becomeActiveFromForeground];
@ -100,50 +107,61 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
} else {
DDLogWarn(@"The app was launched in an unknown way");
}
[[PushManager sharedManager] validateUserNotificationSettings];
[TSPreKeyManager refreshPreKeys];
}
return YES;
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
- (void)setupTSKitEnv {
[TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager;
[[TSStorageManager sharedManager] setupDatabase];
[TextSecureKitEnv sharedEnv].notificationsManager = [[NotificationsManager alloc] init];
}
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[PushManager.sharedManager.pushNotificationFutureSource trySetResult:deviceToken];
}
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
#ifdef DEBUG
DDLogWarn(@"We're in debug mode, and registered a fake push identifier");
[PushManager.sharedManager.pushNotificationFutureSource trySetResult:[@"aFakePushIdentifier" dataUsingEncoding:NSUTF8StringEncoding]];
[PushManager.sharedManager.pushNotificationFutureSource trySetResult:@"aFakePushIdentifier"];
#else
[PushManager.sharedManager.pushNotificationFutureSource trySetFailure:error];
#endif
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
- (void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[PushManager.sharedManager.userNotificationFutureSource trySetResult:notificationSettings];
}
-(BOOL) application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation {
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
if ([url.scheme isEqualToString:kURLSchemeSGNLKey]) {
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![TSAccountManager isRegistered]) {
id signupController = [Environment getCurrent].signUpFlowNavigationController;
id signupController = [Environment getCurrent].signUpFlowNavigationController;
if ([signupController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController*)signupController;
UINavigationController *navController = (UINavigationController *)signupController;
UIViewController *controller = [navController.childViewControllers lastObject];
if ([controller isKindOfClass:[CodeVerificationViewController class]]) {
CodeVerificationViewController *cvvc = (CodeVerificationViewController*)controller;
NSString *verificationCode = [url.path substringFromIndex:1];
cvvc.challengeTextField.text = verificationCode;
CodeVerificationViewController *cvvc = (CodeVerificationViewController *)controller;
NSString *verificationCode = [url.path substringFromIndex:1];
cvvc.challengeTextField.text = verificationCode;
[cvvc verifyChallengeAction:nil];
} else{
DDLogWarn(@"Not the verification view controller we expected. Got %@ instead", NSStringFromClass(controller.class));
} else {
DDLogWarn(@"Not the verification view controller we expected. Got %@ instead",
NSStringFromClass(controller.class));
}
}
} else{
} else {
DDLogWarn(@"Application opened with an unknown URL action: %@", url.host);
}
} else {
@ -152,9 +170,10 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
return NO;
}
-(void)applicationDidBecomeActive:(UIApplication *)application {
- (void)applicationDidBecomeActive:(UIApplication *)application {
if ([TSAccountManager isRegistered]) {
// We're double checking that the app is active, to be sure since we can't verify in production env due to code signing.
// We're double checking that the app is active, to be sure since we can't verify in production env due to code
// signing.
[TSSocketManager becomeActiveFromForeground];
[[Environment getCurrent].contactsManager verifyABPermission];
}
@ -164,56 +183,63 @@ static NSString * const kURLHostVerifyPrefix = @"verify";
- (void)applicationWillResignActive:(UIApplication *)application {
UIBackgroundTaskIdentifier __block bgTask = UIBackgroundTaskInvalid;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([TSAccountManager isRegistered]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self protectScreen];
[[[Environment getCurrent] signalsViewController] updateInboxCountLabel];
});
[TSSocketManager resignActivity];
}
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
if ([TSAccountManager isRegistered]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self protectScreen];
[[[Environment getCurrent] signalsViewController] updateInboxCountLabel];
});
[TSSocketManager resignActivity];
}
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler {
if ([TSAccountManager isRegistered]) {
[[Environment getCurrent].signalsViewController composeNew];
} else {
UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil)
message:@"Someone's exited to send his first message! Register now to send your first message."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil)
message:
@"Someone's exited to send his first message! Register now to send your first message."
preferredStyle:UIAlertControllerStyleAlert];
completionHandler(YES);
[self.window.rootViewController presentViewController:controller animated:YES completion:^{
completionHandler(NO);
}];
[self.window.rootViewController presentViewController:controller
animated:YES
completion:^{
completionHandler(NO);
}];
}
}
- (void)prepareScreenshotProtection{
- (void)prepareScreenshotProtection {
self.blankWindow = ({
UIWindow *window = [[UIWindow alloc] initWithFrame:self.window.bounds];
window.hidden = YES;
window.opaque = YES;
window.userInteractionEnabled = NO;
window.windowLevel = CGFLOAT_MAX;
// There appears to be no more reliable way to get the launchscreen image from an asset bundle
NSDictionary *dict = @{@"320x480" : @"LaunchImage-700",
@"320x568" : @"LaunchImage-700-568h",
@"375x667" : @"LaunchImage-800-667h",
@"414x736" : @"LaunchImage-800-Portrait-736h"};
NSString *key = [NSString stringWithFormat:@"%dx%d", (int)[UIScreen mainScreen].bounds.size.width, (int)[UIScreen mainScreen].bounds.size.height];
NSDictionary *dict = @{
@"320x480" : @"LaunchImage-700",
@"320x568" : @"LaunchImage-700-568h",
@"375x667" : @"LaunchImage-800-667h",
@"414x736" : @"LaunchImage-800-Portrait-736h"
};
NSString *key = [NSString stringWithFormat:@"%dx%d",
(int)[UIScreen mainScreen].bounds.size.width,
(int)[UIScreen mainScreen].bounds.size.height];
UIImage *launchImage = [UIImage imageNamed:dict[key]];
UIImageView *imgView = [[UIImageView alloc] initWithImage:launchImage];
UIViewController *vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
@ -222,46 +248,45 @@ performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
[vc.view addSubview:imgView];
[vc.view setBackgroundColor:[UIColor ows_blackColor]];
window.rootViewController = vc;
window;
});
}
- (void)protectScreen{
if (Environment.preferences.screenSecurityIsEnabled){
- (void)protectScreen {
if (Environment.preferences.screenSecurityIsEnabled) {
self.blankWindow.hidden = NO;
}
}
- (void)removeScreenProtection{
- (void)removeScreenProtection {
if (Environment.preferences.screenSecurityIsEnabled) {
self.blankWindow.hidden = YES;
}
}
-(void)setupAppearance {
- (void)setupAppearance {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[[UINavigationBar appearance] setBarTintColor:[UIColor ows_materialBlueColor]];
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
[[UIBarButtonItem appearanceWhenContainedIn: [UISearchBar class], nil] setTintColor:[UIColor ows_materialBlueColor]];
[[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTintColor:[UIColor ows_materialBlueColor]];
[[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]];
[[UIBarButtonItem appearance] setTintColor:[UIColor whiteColor]];
NSShadow *shadow = [NSShadow new];
[shadow setShadowColor:[UIColor clearColor]];
NSDictionary *navbarTitleTextAttributes = @{
NSForegroundColorAttributeName:[UIColor whiteColor],
NSShadowAttributeName:shadow,
};
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]];
[[UINavigationBar appearance] setTitleTextAttributes:navbarTitleTextAttributes];
NSDictionary *navbarTitleTextAttributes = @{
NSForegroundColorAttributeName : [UIColor whiteColor],
NSShadowAttributeName : shadow,
};
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]];
[[UINavigationBar appearance] setTitleTextAttributes:navbarTitleTextAttributes];
}
#pragma mark Push Notifications Delegate Methods
@ -269,20 +294,38 @@ performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[PushManager sharedManager] application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
[[PushManager sharedManager] application:application didReceiveLocalNotification:notification];
}
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler {
[[PushManager sharedManager] application:application handleActionWithIdentifier:identifier forLocalNotification:notification completionHandler:completionHandler];
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)())completionHandler {
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
completionHandler:completionHandler];
}
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler {
[[PushManager sharedManager] application:application handleActionWithIdentifier:identifier forLocalNotification:notification withResponseInfo:responseInfo completionHandler:completionHandler];
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)())completionHandler {
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
withResponseInfo:responseInfo
completionHandler:completionHandler];
}
/**
@ -292,10 +335,10 @@ performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
if ([self applicationIsActive]) {
return;
}
if (![[TSStorageManager sharedManager] databasePasswordAccessible]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(@"PHONE_NEEDS_UNLOCK", nil);
notification.alertBody = NSLocalizedString(@"PHONE_NEEDS_UNLOCK", nil);
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
exit(0);
}
@ -303,11 +346,11 @@ performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
- (BOOL)applicationIsActive {
UIApplication *app = [UIApplication sharedApplication];
if (app.applicationState == UIApplicationStateActive) {
return YES;
}
return NO;
}

View File

@ -1,12 +1,12 @@
#import <Foundation/Foundation.h>
/**
* Tracks which notifications have already been processed, and which are are seen for the first time.
* Tracks which notifications have already been processed, and which are are seen for the first time.
**/
@interface NotificationTracker : NSObject
+(NotificationTracker*) notificationTracker;
-(BOOL) shouldProcessNotification:(NSDictionary*) notification;
+ (NotificationTracker *)notificationTracker;
- (BOOL)shouldProcessNotification:(NSDictionary *)notification;
@end

View File

@ -1,21 +1,21 @@
#import "NotificationTracker.h"
#import "CryptoTools.h"
#import "FunctionalUtil.h"
#import "NotificationTracker.h"
#define MAX_NOTIFICATIONS_TO_TRACK 100
#define NOTIFICATION_PAYLOAD_KEY @"m"
@implementation NotificationTracker {
NSMutableArray* _witnessedNotifications;
NSMutableArray *_witnessedNotifications;
}
+(NotificationTracker*) notificationTracker {
NotificationTracker* notificationTracker = [NotificationTracker new];
+ (NotificationTracker *)notificationTracker {
NotificationTracker *notificationTracker = [NotificationTracker new];
notificationTracker->_witnessedNotifications = [NSMutableArray new];
return notificationTracker;
}
-(BOOL) shouldProcessNotification:(NSDictionary*) notification {
- (BOOL)shouldProcessNotification:(NSDictionary *)notification {
BOOL should = ![self wasNotificationProcessed:notification];
if (should) {
[self markNotificationAsProcessed:notification];
@ -23,27 +23,27 @@
return should;
}
-(void) markNotificationAsProcessed:(NSDictionary*) notification {
NSData* data = [self getIdForNotification:notification];
- (void)markNotificationAsProcessed:(NSDictionary *)notification {
NSData *data = [self getIdForNotification:notification];
[_witnessedNotifications insertObject:data atIndex:0];
while(MAX_NOTIFICATIONS_TO_TRACK < _witnessedNotifications.count){
while (MAX_NOTIFICATIONS_TO_TRACK < _witnessedNotifications.count) {
[_witnessedNotifications removeLastObject];
}
}
-(BOOL) wasNotificationProcessed:(NSDictionary*) notification {
NSData* data = [self getIdForNotification:notification];
return [_witnessedNotifications any:^int(NSData* previousData) {
return [data isEqualToData:previousData];
- (BOOL)wasNotificationProcessed:(NSDictionary *)notification {
NSData *data = [self getIdForNotification:notification];
return [_witnessedNotifications any:^int(NSData *previousData) {
return [data isEqualToData:previousData];
}];
}
// Uniquely Identify a notification by the hash of the message payload.
-(NSData*) getIdForNotification:(NSDictionary*) notification {
NSData* data = [notification[NOTIFICATION_PAYLOAD_KEY] dataUsingEncoding:NSUTF8StringEncoding];
NSData* notificationHash = [data hashWithSha256];
- (NSData *)getIdForNotification:(NSDictionary *)notification {
NSData *data = [notification[NOTIFICATION_PAYLOAD_KEY] dataUsingEncoding:NSUTF8StringEncoding];
NSData *notificationHash = [data hashWithSha256];
return notificationHash;
}

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9060" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="tuk-0x-yCb">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="tuk-0x-yCb">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Segues with Peek and Pop" minToolsVersion="7.1"/>
</dependencies>
<scenes>
<!--Conversations-->
@ -19,12 +18,11 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lKc-rv-FH5" userLabel="empty state view">
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lKc-rv-FH5" userLabel="empty state view">
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Messages :( Tap Compose to send a message or invite a friend to Signal" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Srx-i1-WhD">
<rect key="frame" x="10" y="57" width="300" height="200"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="200" id="GEd-dY-d8r"/>
<constraint firstAttribute="width" constant="300" id="siA-1a-pO1"/>
@ -34,7 +32,6 @@
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="centerX" secondItem="Srx-i1-WhD" secondAttribute="centerX" id="JId-fq-hNc"/>
@ -43,26 +40,7 @@
</view>
<tableView hidden="YES" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="PaA-ol-uQT">
<rect key="frame" x="0.0" y="64" width="330" height="504"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="cells" id="Ao0-kZ-6Fk">
<rect key="frame" x="0.0" y="28" width="330" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Ao0-kZ-6Fk" id="Kzg-Il-hgK">
<rect key="frame" x="0.0" y="0.0" width="297" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</tableViewCellContentView>
<animations/>
<connections>
<segue destination="DtA-8O-wrT" kind="push" identifier="showSegue" id="ANb-Oa-vQe">
<segue key="commit" inheritsFrom="parent" id="3Aj-hB-zgq"/>
<segue key="preview" inheritsFrom="commit" id="ktS-lA-hcf"/>
</segue>
</connections>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="MY2-bB-USa" id="kop-Y6-6DR"/>
@ -70,7 +48,6 @@
</connections>
</tableView>
</subviews>
<animations/>
<constraints>
<constraint firstItem="PaA-ol-uQT" firstAttribute="leading" secondItem="lKc-rv-FH5" secondAttribute="leading" id="0co-lj-Jsm"/>
<constraint firstAttribute="width" secondItem="lKc-rv-FH5" secondAttribute="width" id="Bu7-qv-yue"/>
@ -105,6 +82,7 @@
<outlet property="tableView" destination="PaA-ol-uQT" id="nQU-tR-wbL"/>
<segue destination="Duq-aU-MmN" kind="modal" identifier="2.0_6.0_Call_Segue" modalPresentationStyle="fullScreen" modalTransitionStyle="crossDissolve" id="gHJ-y4-zWg"/>
<segue destination="lIF-0m-2N3" kind="modal" identifier="showSignupFlow" id="DR8-fx-0PD"/>
<segue destination="DtA-8O-wrT" kind="push" identifier="showSegue" id="njS-JT-0Pa"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dE8-zB-mtF" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -122,7 +100,6 @@
<view key="view" contentMode="scaleToFill" id="5r3-kq-bbI">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<navigationItem key="navigationItem" title="Title" id="mLN-DG-4IU">
@ -168,7 +145,6 @@
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AwQ-ec-WBO">
<rect key="frame" x="0.0" y="220" width="400" height="1"/>
<animations/>
<color key="backgroundColor" white="0.61676562499999998" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="yMD-Sw-sDy"/>
@ -176,7 +152,6 @@
</view>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="btnCamera--white" translatesAutoresizingMaskIntoConstraints="NO" id="HyV-ht-MrM">
<rect key="frame" x="19" y="73" width="75" height="75"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="75" id="Yxr-T5-Jck"/>
<constraint firstAttribute="width" constant="75" id="bRq-v9-94p"/>
@ -184,7 +159,6 @@
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Momo's Fingerprint" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ii9-7N-hCc">
<rect key="frame" x="115" y="35" width="250" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="21" id="epx-el-0T8"/>
</constraints>
@ -194,7 +168,6 @@
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="4" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kBW-ix-mdQ">
<rect key="frame" x="115" y="73" width="250" height="75"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="75" id="jaw-8F-c1C"/>
</constraints>
@ -208,7 +181,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tap to scan another user's fingerprint" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ge0-8K-A4F">
<rect key="frame" x="115" y="160" width="213" height="44"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="44" id="HyF-Kt-KmB"/>
</constraints>
@ -217,7 +189,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="trailing" secondItem="HyV-ht-MrM" secondAttribute="trailing" constant="306" id="1XO-7v-R7T"/>
@ -243,7 +214,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Compare both fingerprints to verify your contact's identity and the integrity of the message." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DV4-GV-ZPf">
<rect key="frame" x="22" y="460" width="356" height="44"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="44" id="bif-t1-qQO"/>
</constraints>
@ -256,7 +226,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WhZ-e0-THb">
<rect key="frame" x="0.0" y="220" width="400" height="1"/>
<animations/>
<color key="backgroundColor" white="0.61676562499999998" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="XgI-5W-uMZ"/>
@ -264,7 +233,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Your Fingerprint" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5De-Jq-XWv">
<rect key="frame" x="115" y="35" width="250" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="21" id="475-f9-1TO"/>
</constraints>
@ -274,7 +242,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="btnQRShow--white" highlightedImage="btnQRShow--white-1" translatesAutoresizingMaskIntoConstraints="NO" id="YOs-e5-ee3">
<rect key="frame" x="19" y="73" width="75" height="75"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="75" id="5IR-xd-6uL"/>
<constraint firstAttribute="height" constant="75" id="Nfl-jf-HzC"/>
@ -282,7 +249,6 @@ A0 09 9A FF A8 8A 09 99</string>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tap to display your fingerprint for another user" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9Wg-UP-VDC">
<rect key="frame" x="115" y="160" width="208" height="43"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="43" id="xb1-B9-MyR"/>
</constraints>
@ -292,7 +258,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="4" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e7E-iS-3Oc">
<rect key="frame" x="115" y="73" width="250" height="75"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="75" id="k9r-Fg-1QJ"/>
</constraints>
@ -305,7 +270,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="9Wg-UP-VDC" firstAttribute="leading" secondItem="B4o-Rc-z68" secondAttribute="leading" constant="115" id="3zq-WB-ASq"/>
@ -331,7 +295,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yoz-ex-1gK">
<rect key="frame" x="19" y="492" width="50" height="50"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="50" id="Ie5-wL-XEo"/>
<constraint firstAttribute="height" constant="50" id="aq2-hO-WY0"/>
@ -345,7 +308,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="bottom" secondItem="Yoz-ex-1gK" secondAttribute="bottom" constant="26" id="EVw-Jr-67j"/>
<constraint firstAttribute="trailing" secondItem="zFI-eF-Feb" secondAttribute="trailing" id="Jfg-Ac-5SC"/>
@ -360,11 +322,9 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstItem="DV4-GV-ZPf" firstAttribute="top" secondItem="kEV-0h-NsX" secondAttribute="top" constant="460" id="tPa-CI-8ak"/>
</constraints>
</view>
<animations/>
<blurEffect style="dark"/>
</visualEffectView>
</subviews>
<animations/>
<constraints>
<constraint firstItem="hdo-6G-gXy" firstAttribute="top" secondItem="VCu-vN-Pjg" secondAttribute="bottom" id="1dB-W2-GMz"/>
<constraint firstAttribute="centerX" secondItem="VCu-vN-Pjg" secondAttribute="centerX" id="5Wt-QM-aQa"/>
@ -414,7 +374,6 @@ A0 09 9A FF A8 8A 09 99</string>
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Fdx-Zk-e27">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="groupMemberCell" id="hyn-Ss-OAa">
@ -423,9 +382,7 @@ A0 09 9A FF A8 8A 09 99</string>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hyn-Ss-OAa" id="4XE-JO-Upr">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</tableViewCellContentView>
<animations/>
</tableViewCell>
</prototypes>
<connections>
@ -453,7 +410,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FVU-rR-RPG">
<rect key="frame" x="16" y="510" width="50" height="50"/>
<animations/>
<state key="normal" image="quit.png">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
@ -463,7 +419,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="I4l-hR-EdB">
<rect key="frame" x="35" y="159" width="250" height="250"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="250" id="WS4-Wb-iXP"/>
<constraint firstAttribute="width" constant="250" id="n5C-hu-0F6"/>
@ -471,7 +426,6 @@ A0 09 9A FF A8 8A 09 99</string>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Your Fingerprint" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Psg-eI-6bn">
<rect key="frame" x="50" y="130" width="220" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="220" id="IoY-EK-Qhk"/>
</constraints>
@ -480,7 +434,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="FVU-rR-RPG" firstAttribute="leading" secondItem="8fX-rK-XSk" secondAttribute="leadingMargin" id="4fp-jK-Pgt"/>
@ -516,7 +469,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bag-cN-IZy">
<rect key="frame" x="16" y="498" width="50" height="50"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="50" id="Odb-rK-4Va"/>
<constraint firstAttribute="height" constant="50" id="ZPA-Mg-Y3j"/>
@ -529,7 +481,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="bag-cN-IZy" firstAttribute="leading" secondItem="Du7-nD-ScY" secondAttribute="leadingMargin" id="vL9-x7-knG"/>
@ -563,7 +514,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="top" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Your Phone Number" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="50y-cV-8aI">
<rect key="frame" x="1" y="190" width="320" height="29"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="29" id="T1b-ph-zPv"/>
</constraints>
@ -573,10 +523,8 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="logoSignal" translatesAutoresizingMaskIntoConstraints="NO" id="hle-PK-Ivk">
<rect key="frame" x="91" y="43" width="138" height="139"/>
<animations/>
</imageView>
</subviews>
<animations/>
<color key="backgroundColor" red="0.12549019607843137" green="0.56470588235294117" blue="0.91764705882352937" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="50y-cV-8aI" secondAttribute="bottom" constant="26" id="9Ep-V2-rAF"/>
@ -592,7 +540,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="O6t-0o-mk5">
<rect key="frame" x="0.0" y="59" width="320" height="1"/>
<animations/>
<color key="backgroundColor" red="0.7999122142791748" green="0.80005049705505371" blue="0.7999035120010376" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="gpg-j8-Tjz"/>
@ -600,7 +547,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VvN-1x-NvR">
<rect key="frame" x="237" y="17" width="67" height="26"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="67" id="0Oj-m4-j2A"/>
<constraint firstAttribute="height" constant="26" id="IlP-zb-7h9"/>
@ -616,7 +562,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ngV-Kv-6Ax">
<rect key="frame" x="20" y="0.0" width="195" height="60"/>
<animations/>
<rect key="contentStretch" x="1" y="0.0" width="1" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="LOh-ty-lH1"/>
@ -630,7 +575,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="4z2-Ss-eUR"/>
@ -649,7 +593,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vpp-VC-z9v">
<rect key="frame" x="0.0" y="59" width="360" height="1"/>
<animations/>
<color key="backgroundColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="SiS-mn-Yud"/>
@ -657,7 +600,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Enter Number" textAlignment="right" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="tTJ-pq-Z9L">
<rect key="frame" x="128" y="19" width="176" height="26"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="176" id="6Ux-PV-sZk"/>
<constraint firstAttribute="height" constant="26" id="bkw-ef-Yt9"/>
@ -671,7 +613,6 @@ A0 09 9A FF A8 8A 09 99</string>
</textField>
<button opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AbN-AU-yk7">
<rect key="frame" x="20" y="18" width="152" height="26"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="26" id="hch-s1-zig"/>
<constraint firstAttribute="width" constant="152" id="tZB-hO-p3n"/>
@ -683,7 +624,6 @@ A0 09 9A FF A8 8A 09 99</string>
</state>
</button>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="tTJ-pq-Z9L" secondAttribute="trailing" constant="16" id="G4f-2B-yOo"/>
@ -701,7 +641,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7IK-hd-tli">
<rect key="frame" x="30" y="1" width="260" height="47"/>
<animations/>
<color key="backgroundColor" red="0.067691504955291748" green="0.55628502368927002" blue="0.92999207973480225" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="47" id="foJ-cw-ajR"/>
@ -718,14 +657,12 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="Uhw-zc-NhO">
<rect key="frame" x="250" y="15" width="20" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="20" id="EN0-Kc-yUf"/>
<constraint firstAttribute="width" constant="20" id="WRq-AE-2aq"/>
</constraints>
</activityIndicatorView>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="47" id="2UQ-03-3A5"/>
@ -737,7 +674,6 @@ A0 09 9A FF A8 8A 09 99</string>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="oS3-t9-5np" firstAttribute="leading" secondItem="Y3I-8Y-pLa" secondAttribute="leadingMargin" constant="-16" id="3EZ-dN-uSz"/>
@ -781,7 +717,6 @@ A0 09 9A FF A8 8A 09 99</string>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="fty-2s-xFJ">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="barTintColor" red="0.067691504955291748" green="0.55628502368927002" blue="0.92999207973480225" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBar>
<connections>
@ -799,12 +734,10 @@ A0 09 9A FF A8 8A 09 99</string>
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="o7w-iD-5XF" userLabel="_1.0__1 Country Code Table">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<searchBar key="tableHeaderView" contentMode="redraw" id="Syd-Xl-kCG" userLabel="__1a Search Bar">
<rect key="frame" x="0.0" y="64" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<animations/>
<textInputTraits key="textInputTraits"/>
<connections>
<outlet property="delegate" destination="nT9-Zl-5R0" id="dR5-Gb-Tvl"/>
@ -820,7 +753,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="United states" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SDz-Ag-lp3">
<rect key="frame" x="20" y="11" width="244" height="22"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="244" id="Y66-3d-yJI"/>
</constraints>
@ -830,7 +762,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="+1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hl9-0w-DHU">
<rect key="frame" x="237" y="12" width="64" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="64" id="S6t-bW-MMl"/>
</constraints>
@ -839,7 +770,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="SDz-Ag-lp3" firstAttribute="leading" secondItem="gVo-Nw-ff7" secondAttribute="leadingMargin" constant="12" id="aYr-BL-A9N"/>
<constraint firstAttribute="centerY" secondItem="SDz-Ag-lp3" secondAttribute="centerY" id="d1E-Q7-R5V"/>
@ -847,7 +777,6 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstAttribute="trailingMargin" secondItem="hl9-0w-DHU" secondAttribute="trailing" constant="11" id="zEI-fy-DdI"/>
</constraints>
</tableViewCellContentView>
<animations/>
<connections>
<outlet property="countryCodeLabel" destination="hl9-0w-DHU" id="z7K-fi-Cag"/>
<outlet property="countryNameLabel" destination="SDz-Ag-lp3" id="aCl-RV-0Vm"/>
@ -904,9 +833,7 @@ A0 09 9A FF A8 8A 09 99</string>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="32G-1m-e8H">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
</view>
<animations/>
<blurEffect style="dark"/>
</visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kYX-We-g3s" userLabel="conversation contact">
@ -914,7 +841,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="eSg-l3-cbz">
<rect key="frame" x="236" y="67" width="62" height="62"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="62" id="SUM-4n-TK8"/>
<constraint firstAttribute="height" constant="62" id="cNa-tL-3WW"/>
@ -922,7 +848,6 @@ A0 09 9A FF A8 8A 09 99</string>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Moxie Marlinspike" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zMG-vs-mRy">
<rect key="frame" x="25" y="56" width="195" height="45"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="45" id="pkz-I0-lQ2"/>
</constraints>
@ -932,7 +857,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="calling mobile ..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dbS-Y4-Ge8">
<rect key="frame" x="25" y="102" width="275" height="28"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="275" id="Jym-Zt-gVp"/>
<constraint firstAttribute="height" constant="28" id="mzl-vA-mVI"/>
@ -942,7 +866,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="eSg-l3-cbz" firstAttribute="trailing" secondItem="zMG-vs-mRy" secondAttribute="trailing" constant="78" id="98G-fD-P9z"/>
<constraint firstItem="eSg-l3-cbz" firstAttribute="top" secondItem="kYX-We-g3s" secondAttribute="top" constant="67" id="Dpw-L2-chG"/>
@ -956,7 +879,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Conversation Safe Words" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bnt-nc-bE8">
<rect key="frame" x="27" y="230" width="265" height="40"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="40" id="DTP-4z-twz"/>
</constraints>
@ -969,7 +891,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cXl-nO-D0h">
<rect key="frame" x="0.0" y="44" width="320" height="65"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="65" id="hmt-mu-kI4"/>
@ -977,7 +898,6 @@ A0 09 9A FF A8 8A 09 99</string>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="watchword chambermaid" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CLR-My-Jfk">
<rect key="frame" x="25" y="60" width="320" height="31"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="31" id="HEv-nI-1Sb"/>
</constraints>
@ -986,7 +906,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="130" id="QYb-BI-B5m"/>
<constraint firstAttribute="bottom" secondItem="CLR-My-Jfk" secondAttribute="bottom" constant="39" id="W1n-ld-j5e"/>
@ -1002,7 +921,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uqh-Gl-dvz" userLabel="Speaker">
<rect key="frame" x="200" y="0.0" width="80" height="80"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="80" id="CCC-wz-iw7"/>
<constraint firstAttribute="height" constant="80" id="LqQ-FB-t9K"/>
@ -1017,7 +935,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4OS-z4-ixd" userLabel="Mute Button">
<rect key="frame" x="40" y="0.0" width="80" height="80"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="80" id="0QK-nC-B88"/>
<constraint firstAttribute="height" constant="80" id="2DL-xO-VaK"/>
@ -1031,7 +948,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="centerY" secondItem="4OS-z4-ixd" secondAttribute="centerY" constant="10" id="4ft-7i-1tH"/>
<constraint firstItem="4OS-z4-ixd" firstAttribute="leading" secondItem="nsz-Sa-Uh6" secondAttribute="leading" constant="40" id="656-YA-2rx"/>
@ -1045,7 +961,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F2a-2h-UK2" userLabel="Hang Up Button">
<rect key="frame" x="120" y="0.0" width="80" height="80"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="80" id="ToV-Ks-2i9"/>
<constraint firstAttribute="height" constant="80" id="ifY-o4-2Pv"/>
@ -1058,7 +973,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="100" id="2YR-lI-F8e"/>
<constraint firstAttribute="centerY" secondItem="F2a-2h-UK2" secondAttribute="centerY" constant="10" id="PVe-Kc-Bix"/>
@ -1070,7 +984,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="De1-JJ-Nsi" userLabel="Decline">
<rect key="frame" x="40" y="0.0" width="80" height="80"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="80" id="GDu-4c-Hm7"/>
<constraint firstAttribute="width" constant="80" id="rKd-Jd-G3l"/>
@ -1084,7 +997,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3b2-qR-0Y3" userLabel="Answer">
<rect key="frame" x="200" y="0.0" width="80" height="80"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="80" id="amq-H9-Gaa"/>
<constraint firstAttribute="width" constant="80" id="wqt-Jl-Jf8"/>
@ -1097,7 +1009,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="centerY" secondItem="3b2-qR-0Y3" secondAttribute="centerY" constant="10" id="EMq-Jz-Poy"/>
<constraint firstAttribute="height" constant="100" id="G8L-Qh-DIj"/>
@ -1107,7 +1018,6 @@ A0 09 9A FF A8 8A 09 99</string>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="87w-Jm-tMc" firstAttribute="centerX" secondItem="vir-2e-LN3" secondAttribute="centerX" id="2gh-0t-obh"/>
@ -1175,7 +1085,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="top" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Verification" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e8Y-At-xEK">
<rect key="frame" x="1" y="191" width="320" height="29"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="29" id="Mem-ez-9kw"/>
</constraints>
@ -1185,10 +1094,8 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="logoSignal" translatesAutoresizingMaskIntoConstraints="NO" id="gVg-vn-tFu">
<rect key="frame" x="92" y="44" width="138" height="139"/>
<animations/>
</imageView>
</subviews>
<animations/>
<color key="backgroundColor" red="0.12549019610000001" green="0.56470588239999997" blue="0.91764705879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="245" id="5bj-kr-L4s" userLabel="Height - (245) - _1.1 Registration Screen Title"/>
@ -1204,7 +1111,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Verification Code" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="yK6-ad-ihc">
<rect key="frame" x="-40" y="0.0" width="401" height="32"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="401" id="aPy-KQ-lfQ"/>
</constraints>
@ -1213,14 +1119,12 @@ A0 09 9A FF A8 8A 09 99</string>
</textField>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="260-Dq-fnd">
<rect key="frame" x="36" y="26" width="248" height="1"/>
<animations/>
<color key="backgroundColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="OPE-jg-ZYz"/>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="260-Dq-fnd" secondAttribute="trailing" constant="36" id="0sO-fC-DMj"/>
@ -1238,7 +1142,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5Nx-nz-ht7">
<rect key="frame" x="36" y="0.0" width="248" height="47"/>
<animations/>
<color key="backgroundColor" red="0.067691504959999996" green="0.55628502369999999" blue="0.92999207969999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="47" id="ote-hy-MTP"/>
@ -1254,14 +1157,12 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="vmS-8s-cvA">
<rect key="frame" x="249" y="14" width="20" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="20" id="IkO-na-VRc"/>
<constraint firstAttribute="height" constant="20" id="rNO-wM-YcD"/>
</constraints>
</activityIndicatorView>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="5Nx-nz-ht7" firstAttribute="leading" secondItem="95o-ce-XGu" secondAttribute="leading" constant="36" id="1C6-X7-eAN"/>
@ -1277,7 +1178,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9dl-sl-tZN">
<rect key="frame" x="36" y="0.0" width="248" height="47"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="47" id="giC-el-dFa"/>
</constraints>
@ -1292,14 +1192,12 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="utk-2S-Qc7">
<rect key="frame" x="250" y="14" width="20" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="20" id="nY2-Ab-Abc"/>
<constraint firstAttribute="width" constant="20" id="w86-JO-TiH"/>
</constraints>
</activityIndicatorView>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="utk-2S-Qc7" firstAttribute="top" secondItem="UWb-Di-S2f" secondAttribute="top" constant="14" id="FZK-Y5-nhN"/>
@ -1315,7 +1213,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IVn-LE-fmp">
<rect key="frame" x="0.0" y="3" width="328" height="56"/>
<animations/>
<color key="backgroundColor" red="0.90186256170272827" green="0.90201729536056519" blue="0.90185278654098511" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="56" id="qQe-8g-3LR"/>
@ -1331,10 +1228,8 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="_arrow_button" translatesAutoresizingMaskIntoConstraints="NO" id="rj8-Z7-7mp">
<rect key="frame" x="8" y="17" width="12" height="23"/>
<animations/>
</imageView>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="IVn-LE-fmp" firstAttribute="width" secondItem="J5A-hy-9hb" secondAttribute="width" constant="8" id="8Dw-FS-ZwO"/>
@ -1351,7 +1246,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2I5-tm-rMi">
<rect key="frame" x="0.0" y="0.0" width="320" height="57"/>
<animations/>
<color key="backgroundColor" red="0.70980392156862748" green="0.80392156862745101" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="57" id="3CX-0k-nDf"/>
@ -1367,7 +1261,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="(555) 555-5555" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wYz-da-3ZB">
<rect key="frame" x="129" y="18" width="164" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="164" id="ElJ-Jb-M1R"/>
</constraints>
@ -1377,14 +1270,12 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="lFE-KR-9iN">
<rect key="frame" x="149" y="19" width="20" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="20" id="S5y-W0-fAs"/>
<constraint firstAttribute="width" constant="20" id="rFK-EJ-Xgu"/>
</constraints>
</activityIndicatorView>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="wYz-da-3ZB" secondAttribute="centerY" id="21A-bh-r9x"/>
@ -1398,7 +1289,6 @@ A0 09 9A FF A8 8A 09 99</string>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="XXt-2F-6KC" firstAttribute="leading" secondItem="Zgb-Uj-73M" secondAttribute="leadingMargin" constant="-16" id="186-I2-PpT"/>
@ -1451,7 +1341,6 @@ A0 09 9A FF A8 8A 09 99</string>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="VNq-cN-pk9">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="barTintColor" red="0.067691504955291748" green="0.55628502368927002" blue="0.92999207973480225" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBar>
<nil name="viewControllers"/>
@ -1470,12 +1359,10 @@ A0 09 9A FF A8 8A 09 99</string>
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="FhQ-dM-1mj">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
<view key="tableFooterView" contentMode="scaleToFill" id="Ezq-Cw-na2">
<rect key="frame" x="0.0" y="502" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
</view>
<sections>
<tableViewSection id="2XP-ps-mfi">
@ -1489,7 +1376,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1 (708) 000-1234" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ipE-BI-sLL">
<rect key="frame" x="0.0" y="58" width="320" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="21" id="hnY-fM-R9C"/>
</constraints>
@ -1499,7 +1385,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tyler Reinhard" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Pu-Cs-0K2">
<rect key="frame" x="0.0" y="28" width="320" height="30"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="30" id="MJo-RF-hP8"/>
</constraints>
@ -1509,7 +1394,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XPv-Ww-lSM">
<rect key="frame" x="0.0" y="80" width="320" height="30"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="30" id="6VH-TG-7PM"/>
</constraints>
@ -1523,7 +1407,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstItem="XPv-Ww-lSM" firstAttribute="leading" secondItem="gr7-Sm-bcs" secondAttribute="leadingMargin" constant="-8" id="0WW-am-sJq"/>
<constraint firstItem="ipE-BI-sLL" firstAttribute="top" secondItem="gr7-Sm-bcs" secondAttribute="topMargin" constant="50" id="4iL-fh-XTs"/>
@ -1536,7 +1419,6 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstItem="ipE-BI-sLL" firstAttribute="leading" secondItem="gr7-Sm-bcs" secondAttribute="leadingMargin" constant="-8" id="zCd-GH-BpM"/>
</constraints>
</tableViewCellContentView>
<animations/>
<inset key="separatorInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</tableViewCell>
</cells>
@ -1552,7 +1434,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Network Status" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uNq-FV-lwt">
<rect key="frame" x="15" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="q6L-Sa-lrA"/>
</constraints>
@ -1562,7 +1443,6 @@ A0 09 9A FF A8 8A 09 99</string>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connected" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tg3-dQ-odw">
<rect key="frame" x="105" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="Lw2-Fv-sOC"/>
<constraint firstAttribute="height" constant="21" id="uvH-QZ-iUw"/>
@ -1572,7 +1452,6 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="tg3-dQ-odw" firstAttribute="top" secondItem="hqv-P5-du9" secondAttribute="topMargin" constant="3" id="C7U-ve-FJA"/>
<constraint firstItem="uNq-FV-lwt" firstAttribute="leading" secondItem="hqv-P5-du9" secondAttribute="leadingMargin" constant="7" id="ViZ-i6-VOo"/>
@ -1580,7 +1459,6 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstAttribute="trailingMargin" secondItem="tg3-dQ-odw" secondAttribute="trailing" constant="7" id="xHz-d7-B39"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
</cells>
</tableViewSection>
@ -1595,7 +1473,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Privacy" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TAs-sK-kbi">
<rect key="frame" x="15" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="70I-Jg-zQp"/>
</constraints>
@ -1604,13 +1481,11 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="TAs-sK-kbi" firstAttribute="leading" secondItem="vOb-SA-SH2" secondAttribute="leadingMargin" constant="7" id="KQc-S9-6h0"/>
<constraint firstAttribute="centerY" secondItem="TAs-sK-kbi" secondAttribute="centerY" id="uj7-Jj-RUr"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="jp5-vZ-AhJ">
<rect key="frame" x="0.0" y="270" width="320" height="44"/>
@ -1621,7 +1496,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notifications" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BaC-fn-VoA">
<rect key="frame" x="15" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="HeG-Yg-7m7"/>
</constraints>
@ -1630,13 +1504,11 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="BaC-fn-VoA" firstAttribute="leading" secondItem="sji-CJ-bhq" secondAttribute="leadingMargin" constant="7" id="Tae-ZO-Fxf"/>
<constraint firstAttribute="centerY" secondItem="BaC-fn-VoA" secondAttribute="centerY" id="gMd-UT-AWs"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="Xx7-pz-aLN">
<rect key="frame" x="0.0" y="314" width="320" height="44"/>
@ -1647,7 +1519,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qix-5C-dh1">
<rect key="frame" x="15" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="QQd-lT-7aN"/>
</constraints>
@ -1656,13 +1527,11 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="centerY" secondItem="qix-5C-dh1" secondAttribute="centerY" id="LKc-Zd-L2y"/>
<constraint firstItem="qix-5C-dh1" firstAttribute="leading" secondItem="pMA-vR-8Ae" secondAttribute="leadingMargin" constant="7" id="oDP-br-7h2"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="EI4-kQ-MMA">
<rect key="frame" x="0.0" y="358" width="320" height="44"/>
@ -1673,7 +1542,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="About" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6FV-LR-9Hv">
<rect key="frame" x="15" y="11" width="200" height="21"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="200" id="IIS-zH-XcG"/>
</constraints>
@ -1682,13 +1550,11 @@ A0 09 9A FF A8 8A 09 99</string>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="6FV-LR-9Hv" firstAttribute="leading" secondItem="czg-5p-aVz" secondAttribute="leadingMargin" constant="7" id="9MM-zs-eHh"/>
<constraint firstAttribute="centerY" secondItem="6FV-LR-9Hv" secondAttribute="centerY" id="Rdb-IM-Koi"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
</cells>
</tableViewSection>
@ -1703,7 +1569,6 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4Mk-ly-6fq">
<rect key="frame" x="15" y="25" width="285" height="50"/>
<animations/>
<color key="backgroundColor" red="1" green="0.2196078431372549" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="Pwj-l2-Tav"/>
@ -1717,14 +1582,12 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</button>
</subviews>
<animations/>
<constraints>
<constraint firstItem="4Mk-ly-6fq" firstAttribute="leading" secondItem="Ok9-fE-WhB" secondAttribute="leadingMargin" constant="7" id="0dH-Hz-wMU"/>
<constraint firstAttribute="trailingMargin" secondItem="4Mk-ly-6fq" secondAttribute="trailing" constant="12" id="Hwq-uT-qGp"/>
<constraint firstItem="4Mk-ly-6fq" firstAttribute="top" secondItem="Ok9-fE-WhB" secondAttribute="topMargin" constant="17" id="TQa-bs-YNu"/>
</constraints>
</tableViewCellContentView>
<animations/>
</tableViewCell>
</cells>
</tableViewSection>
@ -1770,7 +1633,6 @@ A0 09 9A FF A8 8A 09 99</string>
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="gzw-fh-en2">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="barTintColor" red="0.067691504955291748" green="0.55628502368927002" blue="0.92999207973480225" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBar>
<nil name="viewControllers"/>
@ -1789,7 +1651,6 @@ A0 09 9A FF A8 8A 09 99</string>
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="X9S-Pa-EbX">
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="ContactTableViewCell" rowHeight="48" id="Ld5-sX-pB8" customClass="ContactTableViewCell">
@ -1801,13 +1662,11 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="urb-Me-knG">
<rect key="frame" x="20" y="4" width="220" height="39"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="bottomMargin" secondItem="urb-Me-knG" secondAttribute="bottom" constant="-4" id="3fF-H3-Spz"/>
<constraint firstAttribute="trailingMargin" secondItem="urb-Me-knG" secondAttribute="trailing" constant="72" id="R9g-GE-nWU"/>
@ -1815,7 +1674,6 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstItem="urb-Me-knG" firstAttribute="top" secondItem="EqP-87-4hZ" secondAttribute="topMargin" constant="-4" id="iex-zH-iTD"/>
</constraints>
</tableViewCellContentView>
<animations/>
<connections>
<outlet property="nameLabel" destination="urb-Me-knG" id="2h5-l1-QDQ"/>
</connections>
@ -1858,15 +1716,14 @@ A0 09 9A FF A8 8A 09 99</string>
<viewControllerLayoutGuide type="bottom" id="kH6-9L-pzh"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="P0X-AM-Yjw">
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ukg-om-VX3">
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ukg-om-VX3">
<rect key="frame" x="0.0" y="0.0" width="320" height="100"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ul8-NY-i4c">
<rect key="frame" x="8" y="20" width="60" height="60"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="60" id="Fkg-iq-6Ap"/>
<constraint firstAttribute="width" constant="60" id="l0W-ug-b1c"/>
@ -1880,7 +1737,6 @@ A0 09 9A FF A8 8A 09 99</string>
</button>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name this group chat" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="gbm-B5-gCc">
<rect key="frame" x="86" y="35" width="221" height="30"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="30" id="bKM-dc-IsP"/>
</constraints>
@ -1888,7 +1744,6 @@ A0 09 9A FF A8 8A 09 99</string>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" returnKeyType="done" enablesReturnKeyAutomatically="YES"/>
</textField>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="gbm-B5-gCc" secondAttribute="trailing" constant="13" id="1Pn-JF-Njp"/>
@ -1899,9 +1754,8 @@ A0 09 9A FF A8 8A 09 99</string>
<constraint firstItem="gbm-B5-gCc" firstAttribute="leading" secondItem="Ul8-NY-i4c" secondAttribute="trailing" constant="18" id="yn3-rt-mGC"/>
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsMultipleSelection="YES" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="cFo-AT-Srf">
<tableView clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsMultipleSelection="YES" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="cFo-AT-Srf">
<rect key="frame" x="0.0" y="108" width="320" height="396"/>
<animations/>
<color key="backgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="ekO-kw-iHV">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
@ -1909,13 +1763,11 @@ A0 09 9A FF A8 8A 09 99</string>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add people:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="daD-wf-IGn">
<rect key="frame" x="10" y="274" width="130" height="21"/>
<animations/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="daD-wf-IGn" firstAttribute="leading" secondItem="ekO-kw-iHV" secondAttribute="leading" constant="10" id="00e-AP-Ygd"/>
@ -1934,15 +1786,12 @@ A0 09 9A FF A8 8A 09 99</string>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="a4j-OQ-ala">
<rect key="frame" x="15" y="0.0" width="290" height="59"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="20"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
</tableViewCellContentView>
<animations/>
</tableViewCell>
</prototypes>
<connections>
@ -1951,7 +1800,6 @@ A0 09 9A FF A8 8A 09 99</string>
</connections>
</tableView>
</subviews>
<animations/>
<color key="backgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="cFo-AT-Srf" firstAttribute="centerX" secondItem="P0X-AM-Yjw" secondAttribute="centerX" id="EUd-V6-lGx"/>
@ -1990,12 +1838,10 @@ A0 09 9A FF A8 8A 09 99</string>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="l1Z-lc-H46">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</navigationBar>
<nil name="viewControllers"/>
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="D2L-cq-DaY">
<autoresizingMask key="autoresizingMask"/>
<animations/>
</toolbar>
<connections>
<segue destination="sL4-Zw-2Og" kind="relationship" relationship="rootViewController" id="zqL-2e-WXU"/>
@ -2027,8 +1873,7 @@ A0 09 9A FF A8 8A 09 99</string>
</resources>
<inferredMetricsTieBreakers>
<segue reference="DR8-fx-0PD"/>
<segue reference="ANb-Oa-vQe"/>
<segue reference="xo7-5J-BJb"/>
<segue reference="D0d-4f-lcI"/>
<segue reference="gZ1-lh-srF"/>
</inferredMetricsTieBreakers>
</document>

View File

@ -10,22 +10,22 @@
@interface UIColor (OWS)
+ (UIColor*) ows_materialBlueColor;
+ (UIColor *)ows_materialBlueColor;
+ (UIColor *) ows_fadedBlueColor;
+ (UIColor *)ows_fadedBlueColor;
+ (UIColor *) ows_darkBackgroundColor;
+ (UIColor *)ows_darkBackgroundColor;
+ (UIColor *) ows_darkGrayColor;
+ (UIColor *)ows_darkGrayColor;
+ (UIColor *) ows_yellowColor;
+ (UIColor *)ows_yellowColor;
+ (UIColor *) ows_greenColor;
+ (UIColor *)ows_greenColor;
+ (UIColor *) ows_redColor;
+ (UIColor *)ows_redColor;
+ (UIColor*) ows_blackColor;
+ (UIColor *)ows_blackColor;
+ (UIColor*) backgroundColorForContact:(NSString*)contactIdentifier;
+ (UIColor *)backgroundColorForContact:(NSString *)contactIdentifier;
@end

View File

@ -6,79 +6,81 @@
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import "UIColor+OWS.h"
#import "Cryptography.h"
#import "UIColor+OWS.h"
@implementation UIColor (OWS)
+ (UIColor*) ows_materialBlueColor {
+ (UIColor *)ows_materialBlueColor {
// blue: #2090EA
return [UIColor colorWithRed:32.f/255.f green:144.f/255.f blue:234.f/255.f alpha:1.f];
return [UIColor colorWithRed:32.f / 255.f green:144.f / 255.f blue:234.f / 255.f alpha:1.f];
}
+ (UIColor*) ows_blackColor {
+ (UIColor *)ows_blackColor {
// black: #080A00
return [UIColor colorWithRed:8.f/255.f green:10.f/255.f blue:0./255.f alpha:1.f];
return [UIColor colorWithRed:8.f / 255.f green:10.f / 255.f blue:0. / 255.f alpha:1.f];
}
+ (UIColor*) ows_darkGrayColor {
return [UIColor colorWithRed:81.f/255.f green:81.f/255.f blue:81.f/255.f alpha:1.f];
+ (UIColor *)ows_darkGrayColor {
return [UIColor colorWithRed:81.f / 255.f green:81.f / 255.f blue:81.f / 255.f alpha:1.f];
}
+ (UIColor*) ows_darkBackgroundColor {
return [UIColor colorWithRed:35.f/255.f green:31.f/255.f blue:32.f/255.f alpha:1.f];
+ (UIColor *)ows_darkBackgroundColor {
return [UIColor colorWithRed:35.f / 255.f green:31.f / 255.f blue:32.f / 255.f alpha:1.f];
}
+ (UIColor *) ows_fadedBlueColor {
+ (UIColor *)ows_fadedBlueColor {
// blue: #B6DEF4
return [UIColor colorWithRed:182.f/255.f green:222.f/255.f blue:244.f/255.f alpha:1.f];
return [UIColor colorWithRed:182.f / 255.f green:222.f / 255.f blue:244.f / 255.f alpha:1.f];
}
+ (UIColor *) ows_yellowColor {
+ (UIColor *)ows_yellowColor {
// gold: #FFBB5C
return [UIColor colorWithRed:245.f/255.f green:186.f/255.f blue:98.f/255.f alpha:1.f];
return [UIColor colorWithRed:245.f / 255.f green:186.f / 255.f blue:98.f / 255.f alpha:1.f];
}
+ (UIColor *) ows_greenColor {
+ (UIColor *)ows_greenColor {
// green: #BF4240
return [UIColor colorWithRed:66.f/255.f green:191.f/255.f blue:64.f/255.f alpha:1.f];
return [UIColor colorWithRed:66.f / 255.f green:191.f / 255.f blue:64.f / 255.f alpha:1.f];
}
+ (UIColor *) ows_redColor {
+ (UIColor *)ows_redColor {
// red: #FF3867
return [UIColor colorWithRed:255./255.f green:56.f/255.f blue:103.f/255.f alpha:1.f];
return [UIColor colorWithRed:255. / 255.f green:56.f / 255.f blue:103.f / 255.f alpha:1.f];
}
+ (UIColor *) ows_lightBackgroundColor {
return [UIColor colorWithRed:242.f/255.f green:242.f/255.f blue:242.f/255.f alpha:1.f];
+ (UIColor *)ows_lightBackgroundColor {
return [UIColor colorWithRed:242.f / 255.f green:242.f / 255.f blue:242.f / 255.f alpha:1.f];
}
+ (UIColor*) backgroundColorForContact:(NSString*)contactIdentifier {
NSArray *colors = @[[UIColor colorWithRed:204.f/255.f green:148.f/255.f blue:102.f/255.f alpha:1.f],
[UIColor colorWithRed:187.f/255.f green:104.f/255.f blue:62.f/255.f alpha:1.f],
[UIColor colorWithRed:145.f/255.f green:78.f/255.f blue:48.f/255.f alpha:1.f],
[UIColor colorWithRed:122.f/255.f green:63.f/255.f blue:41.f/255.f alpha:1.f],
[UIColor colorWithRed:80.f/255.f green:46.f/255.f blue:27.f/255.f alpha:1.f],
[UIColor colorWithRed:57.f/255.f green:45.f/255.f blue:19.f/255.f alpha:1.f],
[UIColor colorWithRed:37.f/255.f green:38.f/255.f blue:13.f/255.f alpha:1.f],
[UIColor colorWithRed:23.f/255.f green:31.f/255.f blue:10.f/255.f alpha:1.f],
[UIColor colorWithRed:6.f/255.f green:19.f/255.f blue:10.f/255.f alpha:1.f],
[UIColor colorWithRed:13.f/255.f green:4.f/255.f blue:16.f/255.f alpha:1.f],
[UIColor colorWithRed:27.f/255.f green:12.f/255.f blue:44.f/255.f alpha:1.f],
[UIColor colorWithRed:18.f/255.f green:17.f/255.f blue:64.f/255.f alpha:1.f],
[UIColor colorWithRed:20.f/255.f green:42.f/255.f blue:77.f/255.f alpha:1.f],
[UIColor colorWithRed:18.f/255.f green:55.f/255.f blue:68.f/255.f alpha:1.f],
[UIColor colorWithRed:18.f/255.f green:68.f/255.f blue:61.f/255.f alpha:1.f],
[UIColor colorWithRed:19.f/255.f green:73.f/255.f blue:26.f/255.f alpha:1.f],
[UIColor colorWithRed:13.f/255.f green:48.f/255.f blue:15.f/255.f alpha:1.f],
[UIColor colorWithRed:44.f/255.f green:165.f/255.f blue:137.f/255.f alpha:1.f],
[UIColor colorWithRed:137.f/255.f green:181.f/255.f blue:48.f/255.f alpha:1.f],
[UIColor colorWithRed:208.f/255.f green:204.f/255.f blue:78.f/255.f alpha:1.f],
[UIColor colorWithRed:227.f/255.f green:162.f/255.f blue:150.f/255.f alpha:1.f]];
+ (UIColor *)backgroundColorForContact:(NSString *)contactIdentifier {
NSArray *colors = @[
[UIColor colorWithRed:204.f / 255.f green:148.f / 255.f blue:102.f / 255.f alpha:1.f],
[UIColor colorWithRed:187.f / 255.f green:104.f / 255.f blue:62.f / 255.f alpha:1.f],
[UIColor colorWithRed:145.f / 255.f green:78.f / 255.f blue:48.f / 255.f alpha:1.f],
[UIColor colorWithRed:122.f / 255.f green:63.f / 255.f blue:41.f / 255.f alpha:1.f],
[UIColor colorWithRed:80.f / 255.f green:46.f / 255.f blue:27.f / 255.f alpha:1.f],
[UIColor colorWithRed:57.f / 255.f green:45.f / 255.f blue:19.f / 255.f alpha:1.f],
[UIColor colorWithRed:37.f / 255.f green:38.f / 255.f blue:13.f / 255.f alpha:1.f],
[UIColor colorWithRed:23.f / 255.f green:31.f / 255.f blue:10.f / 255.f alpha:1.f],
[UIColor colorWithRed:6.f / 255.f green:19.f / 255.f blue:10.f / 255.f alpha:1.f],
[UIColor colorWithRed:13.f / 255.f green:4.f / 255.f blue:16.f / 255.f alpha:1.f],
[UIColor colorWithRed:27.f / 255.f green:12.f / 255.f blue:44.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:17.f / 255.f blue:64.f / 255.f alpha:1.f],
[UIColor colorWithRed:20.f / 255.f green:42.f / 255.f blue:77.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:55.f / 255.f blue:68.f / 255.f alpha:1.f],
[UIColor colorWithRed:18.f / 255.f green:68.f / 255.f blue:61.f / 255.f alpha:1.f],
[UIColor colorWithRed:19.f / 255.f green:73.f / 255.f blue:26.f / 255.f alpha:1.f],
[UIColor colorWithRed:13.f / 255.f green:48.f / 255.f blue:15.f / 255.f alpha:1.f],
[UIColor colorWithRed:44.f / 255.f green:165.f / 255.f blue:137.f / 255.f alpha:1.f],
[UIColor colorWithRed:137.f / 255.f green:181.f / 255.f blue:48.f / 255.f alpha:1.f],
[UIColor colorWithRed:208.f / 255.f green:204.f / 255.f blue:78.f / 255.f alpha:1.f],
[UIColor colorWithRed:227.f / 255.f green:162.f / 255.f blue:150.f / 255.f alpha:1.f]
];
NSData *contactData = [contactIdentifier dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger hashingLength = 8;
unsigned long long choose;
NSData *hashData = [Cryptography computeSHA256:contactData truncatedToBytes:hashingLength];
@ -88,4 +90,3 @@
@end

View File

@ -11,7 +11,7 @@
* Audio Settings, and interfacing with the OS. The Call Audio Pipeline it self is delegated
* to the RemoteIOAudio Class.
*
* The Audio Profile determines which preset of logic to use for playing sounds, Such as
* The Audio Profile determines which preset of logic to use for playing sounds, Such as
* which speaker to use or if all sounds should be muted.
**/
@ -22,24 +22,24 @@ enum AudioProfile {
AudioProfile_ExternalSpeaker,
};
+(AppAudioManager*) sharedInstance;
+ (AppAudioManager *)sharedInstance;
-(void) setAudioProfile:(enum AudioProfile) profile;
-(enum AudioProfile) getCurrentAudioProfile;
-(void) updateAudioRouter;
- (void)setAudioProfile:(enum AudioProfile)profile;
- (enum AudioProfile)getCurrentAudioProfile;
- (void)updateAudioRouter;
-(void) respondToProgressChange:(enum CallProgressType) progressType forLocallyInitiatedCall:(BOOL) initiatedLocally;
-(void) respondToTerminationType:(enum CallTerminationType) terminationType;
- (void)respondToProgressChange:(enum CallProgressType)progressType forLocallyInitiatedCall:(BOOL)initiatedLocally;
- (void)respondToTerminationType:(enum CallTerminationType)terminationType;
-(BOOL) toggleSpeakerPhone;
-(void) cancellAllAudio;
- (BOOL)toggleSpeakerPhone;
- (void)cancellAllAudio;
-(void) requestRequiredPermissionsIfNeededWithCompletion:(PermissionBlock)permissionBlock incoming:(BOOL)isIncoming;
-(BOOL) requestRecordingPrivilege;
-(BOOL) releaseRecordingPrivilege;
- (void)requestRequiredPermissionsIfNeededWithCompletion:(PermissionBlock)permissionBlock incoming:(BOOL)isIncoming;
- (BOOL)requestRecordingPrivilege;
- (BOOL)releaseRecordingPrivilege;
-(BOOL) setAudioEnabled:(BOOL) enable;
-(void) awake;
- (BOOL)setAudioEnabled:(BOOL)enable;
- (void)awake;
- (void)didCompleteSoundInstanceOfType:(SoundInstanceType)instanceType;

View File

@ -8,9 +8,9 @@
#define RECORDING_CATEGORY AVAudioSessionCategoryPlayAndRecord
AppAudioManager* sharedAppAudioManager;
AppAudioManager *sharedAppAudioManager;
@interface AppAudioManager () <UIAlertViewDelegate>{
@interface AppAudioManager () <UIAlertViewDelegate> {
enum AudioProfile _audioProfile;
BOOL isSpeakerphoneActive;
}
@ -20,10 +20,10 @@ AppAudioManager* sharedAppAudioManager;
@implementation AppAudioManager
+(AppAudioManager*) sharedInstance {
@synchronized(self){
if( nil == sharedAppAudioManager){
sharedAppAudioManager = [AppAudioManager new];
+ (AppAudioManager *)sharedInstance {
@synchronized(self) {
if (nil == sharedAppAudioManager) {
sharedAppAudioManager = [AppAudioManager new];
sharedAppAudioManager.soundPlayer = [SoundPlayer new];
[[sharedAppAudioManager soundPlayer] setDelegate:sharedAppAudioManager];
}
@ -33,15 +33,15 @@ AppAudioManager* sharedAppAudioManager;
#pragma mark AudioState Management
-(void) setAudioProfile:(enum AudioProfile) profile {
- (void)setAudioProfile:(enum AudioProfile)profile {
[self updateAudioRouter];
_audioProfile = profile;
_audioProfile = profile;
}
-(void) updateAudioRouter{
if (isSpeakerphoneActive){
- (void)updateAudioRouter {
if (isSpeakerphoneActive) {
[AudioRouter routeAllAudioToExternalSpeaker];
}else{
} else {
switch (_audioProfile) {
case AudioProfile_Default:
[AudioRouter routeAllAudioToInteralSpeaker];
@ -56,24 +56,23 @@ AppAudioManager* sharedAppAudioManager;
}
-(void) overrideAudioProfile{
- (void)overrideAudioProfile {
isSpeakerphoneActive = YES;
[self updateAudioRouter];
}
-(void) resetOverride{
- (void)resetOverride {
isSpeakerphoneActive = NO;
[self updateAudioRouter];
}
-(enum AudioProfile) getCurrentAudioProfile{
- (enum AudioProfile)getCurrentAudioProfile {
return (isSpeakerphoneActive) ? AudioProfile_ExternalSpeaker : _audioProfile;
}
#pragma mark AudioControl;
-(void) respondToProgressChange:(enum CallProgressType) progressType
forLocallyInitiatedCall:(BOOL) initiatedLocally {
switch (progressType){
- (void)respondToProgressChange:(enum CallProgressType)progressType forLocallyInitiatedCall:(BOOL)initiatedLocally {
switch (progressType) {
case CallProgressType_Connecting:
[sharedAppAudioManager setAudioEnabled:YES];
case CallProgressType_Ringing:
@ -90,101 +89,102 @@ AppAudioManager* sharedAppAudioManager;
}
}
-(void) respondToTerminationType:(enum CallTerminationType) terminationType {
if(terminationType == CallTerminationType_ResponderIsBusy) {
- (void)respondToTerminationType:(enum CallTerminationType)terminationType {
if (terminationType == CallTerminationType_ResponderIsBusy) {
[_soundPlayer playSound:[SoundBoard instanceOfBusySound]];
}
else if([self shouldErrorSoundBePlayedForCallTerminationType:terminationType]){
} else if ([self shouldErrorSoundBePlayedForCallTerminationType:terminationType]) {
[_soundPlayer playSound:[SoundBoard instanceOfErrorAlert]];
}
else {
} else {
[_soundPlayer playSound:[SoundBoard instanceOfAlert]];
}
}
-(BOOL) shouldErrorSoundBePlayedForCallTerminationType:(enum CallTerminationType) type{
- (BOOL)shouldErrorSoundBePlayedForCallTerminationType:(enum CallTerminationType)type {
[_soundPlayer stopAllAudio];
if (type == CallTerminationType_RejectedLocal ||
type == CallTerminationType_RejectedRemote ||
type == CallTerminationType_HangupLocal ||
type == CallTerminationType_HangupRemote ||
if (type == CallTerminationType_RejectedLocal || type == CallTerminationType_RejectedRemote ||
type == CallTerminationType_HangupLocal || type == CallTerminationType_HangupRemote ||
type == CallTerminationType_RecipientUnavailable) {
return NO;
}
return YES;
}
-(void) handleInboundRing {
- (void)handleInboundRing {
[_soundPlayer playSound:[SoundBoard instanceOfInboundRingtone]];
}
-(void) handleOutboundRing {
- (void)handleOutboundRing {
[self setAudioProfile:AudioProfile_Default];
[_soundPlayer playSound:[SoundBoard instanceOfOutboundRingtone]];
}
-(void) handleSecuring {
- (void)handleSecuring {
[_soundPlayer stopAllAudio];
[self setAudioProfile:AudioProfile_Default];
[_soundPlayer playSound:[SoundBoard instanceOfHandshakeSound]];
}
-(void) handleCallEstablished {
- (void)handleCallEstablished {
[_soundPlayer stopAllAudio];
[self setAudioProfile:AudioProfile_Default];
[_soundPlayer playSound:[SoundBoard instanceOfCompletedSound]];
}
-(BOOL) toggleSpeakerPhone {
isSpeakerphoneActive=!isSpeakerphoneActive;
- (BOOL)toggleSpeakerPhone {
isSpeakerphoneActive = !isSpeakerphoneActive;
[self updateAudioRouter];
return isSpeakerphoneActive;
}
#pragma mark Audio Control
-(void) cancellAllAudio {
- (void)cancellAllAudio {
[_soundPlayer stopAllAudio];
}
-(BOOL) requestRecordingPrivilege {
- (BOOL)requestRecordingPrivilege {
return [self changeAudioSessionCategoryTo:RECORDING_CATEGORY];
}
-(BOOL) releaseRecordingPrivilege{
- (BOOL)releaseRecordingPrivilege {
return [self changeAudioSessionCategoryTo:DEFAULT_CATEGORY];
}
-(void) requestRequiredPermissionsIfNeededWithCompletion:(PermissionBlock)permissionBlock incoming:(BOOL)isIncoming {
- (void)requestRequiredPermissionsIfNeededWithCompletion:(PermissionBlock)permissionBlock incoming:(BOOL)isIncoming {
[AVAudioSession.sharedInstance requestRecordPermission:^(BOOL granted) {
if (!granted) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"") message:NSLocalizedString(@"AUDIO_PERMISSION_MESSAGE", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"") otherButtonTitles:NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE",nil), nil];
[alertView setDelegate:self];
[alertView show];
}
permissionBlock(granted);
if (!granted) {
UIAlertView *alertView =
[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ACTION_REQUIRED_TITLE", @"")
message:NSLocalizedString(@"AUDIO_PERMISSION_MESSAGE", @"")
delegate:nil
cancelButtonTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
otherButtonTitles:NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", nil), nil];
[alertView setDelegate:self];
[alertView show];
}
permissionBlock(granted);
}];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) { // Tapped the Settings button
NSURL *appSettings = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:appSettings];
}
}
-(BOOL) changeAudioSessionCategoryTo:(NSString*) category {
NSError* e;
- (BOOL)changeAudioSessionCategoryTo:(NSString *)category {
NSError *e;
[AVAudioSession.sharedInstance setCategory:category error:&e];
return (nil != e);
}
-(BOOL) setAudioEnabled:(BOOL) enable {
NSError* e;
- (BOOL)setAudioEnabled:(BOOL)enable {
NSError *e;
if (enable) {
[[AVAudioSession sharedInstance] setActive:enable error:&e];
[_soundPlayer awake];
@ -193,22 +193,21 @@ AppAudioManager* sharedAppAudioManager;
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:&e];
}
return ( nil !=e );
return (nil != e);
}
-(void) awake {
- (void)awake {
[_soundPlayer awake];
}
#pragma mark Sound Player Delegate
- (void)didCompleteSoundInstanceOfType:(SoundInstanceType)instanceType {
if (instanceType == SoundInstanceTypeBusySound ||
instanceType == SoundInstanceTypeErrorAlert ||
if (instanceType == SoundInstanceTypeBusySound || instanceType == SoundInstanceTypeErrorAlert ||
instanceType == SoundInstanceTypeAlert) {
[sharedAppAudioManager setAudioEnabled:NO];
}
}
@end

View File

@ -3,14 +3,14 @@
/**
* Interfaces with OS to control which hardware devices audio is routed to
**/
@interface AudioRouter : NSObject
+(void) restoreDefaults;
+(void) routeAllAudioToInteralSpeaker;
+(void) routeAllAudioToExternalSpeaker;
+ (void)restoreDefaults;
+ (void)routeAllAudioToInteralSpeaker;
+ (void)routeAllAudioToExternalSpeaker;
+(BOOL) isOutputRoutedToSpeaker;
+(BOOL) isOutputRoutedToReciever;
+ (BOOL)isOutputRoutedToSpeaker;
+ (BOOL)isOutputRoutedToReciever;
@end

View File

@ -6,40 +6,40 @@
@implementation AudioRouter
+(void) restoreDefaults {
AVAudioSession* session = AVAudioSession.sharedInstance;
+ (void)restoreDefaults {
AVAudioSession *session = AVAudioSession.sharedInstance;
[session setActive:YES error:nil];
[AudioRouter routeAllAudioToExternalSpeaker];
}
+(void) routeAllAudioToInteralSpeaker {
AVAudioSession* session = AVAudioSession.sharedInstance;
+ (void)routeAllAudioToInteralSpeaker {
AVAudioSession *session = AVAudioSession.sharedInstance;
[session setCategory:DEFAULT_CATAGORY error:nil];
}
+(void) routeAllAudioToExternalSpeaker {
AVAudioSession* session = AVAudioSession.sharedInstance;
+ (void)routeAllAudioToExternalSpeaker {
AVAudioSession *session = AVAudioSession.sharedInstance;
[session setCategory:DEFAULT_CATAGORY withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
}
+(BOOL) isOutputRoutedToSpeaker{
AVAudioSession* session = AVAudioSession.sharedInstance;
AVAudioSessionRouteDescription* routeDesc = session.currentRoute;
for( AVAudioSessionPortDescription* portDesc in routeDesc.outputs){
if (AVAudioSessionPortBuiltInSpeaker == [portDesc portType]){
+ (BOOL)isOutputRoutedToSpeaker {
AVAudioSession *session = AVAudioSession.sharedInstance;
AVAudioSessionRouteDescription *routeDesc = session.currentRoute;
for (AVAudioSessionPortDescription *portDesc in routeDesc.outputs) {
if (AVAudioSessionPortBuiltInSpeaker == [portDesc portType]) {
return YES;
}
}
return NO;
}
+(BOOL) isOutputRoutedToReciever{
AVAudioSession* session = AVAudioSession.sharedInstance;
AVAudioSessionRouteDescription* routeDesc = session.currentRoute;
for( AVAudioSessionPortDescription* portDesc in routeDesc.outputs){
if (AVAudioSessionPortBuiltInReceiver == [portDesc portType]){
+ (BOOL)isOutputRoutedToReciever {
AVAudioSession *session = AVAudioSession.sharedInstance;
AVAudioSessionRouteDescription *routeDesc = session.currentRoute;
for (AVAudioSessionPortDescription *portDesc in routeDesc.outputs) {
if (AVAudioSessionPortBuiltInReceiver == [portDesc portType]) {
return YES;
}
}

View File

@ -3,19 +3,19 @@
#import "SoundInstance.h"
/**
* Factory class for generating Instances of specific Sound files. These are then maintained
* Factory class for generating Instances of specific Sound files. These are then maintained
* and controlled from the SoundPlayer class. This class should mask the use of any specific
* soundFiles.
**/
@interface SoundBoard : NSObject
+(SoundInstance*) instanceOfInboundRingtone;
+(SoundInstance*) instanceOfOutboundRingtone;
+(SoundInstance*) instanceOfHandshakeSound;
+(SoundInstance*) instanceOfCompletedSound;
+(SoundInstance*) instanceOfBusySound;
+(SoundInstance*) instanceOfErrorAlert;
+(SoundInstance*) instanceOfAlert;
+ (SoundInstance *)instanceOfInboundRingtone;
+ (SoundInstance *)instanceOfOutboundRingtone;
+ (SoundInstance *)instanceOfHandshakeSound;
+ (SoundInstance *)instanceOfCompletedSound;
+ (SoundInstance *)instanceOfBusySound;
+ (SoundInstance *)instanceOfErrorAlert;
+ (SoundInstance *)instanceOfAlert;
@end

View File

@ -1,61 +1,60 @@
#import "SoundBoard.h"
static NSString* SoundFile_Alert =@"171756__nenadsimic__picked-coin-echo-2.wav";
static NSString* SoundFile_Busy =@"busy.mp3";
static NSString* SoundFile_Completed =@"completed.mp3";
static NSString* SoundFile_Failure =@"failure.mp3";
static NSString* SoundFile_Handshake =@"handshake.mp3";
static NSString* SoundFile_Outbound =@"outring.mp3";
static NSString* SoundFile_Ringtone =@"r.caf";
static NSString *SoundFile_Alert = @"171756__nenadsimic__picked-coin-echo-2.wav";
static NSString *SoundFile_Busy = @"busy.mp3";
static NSString *SoundFile_Completed = @"completed.mp3";
static NSString *SoundFile_Failure = @"failure.mp3";
static NSString *SoundFile_Handshake = @"handshake.mp3";
static NSString *SoundFile_Outbound = @"outring.mp3";
static NSString *SoundFile_Ringtone = @"r.caf";
@implementation SoundBoard
+(SoundInstance*) instanceOfInboundRingtone {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Ringtone];
+ (SoundInstance *)instanceOfInboundRingtone {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Ringtone];
[soundInstance setAudioToLoopIndefinitely];
[soundInstance setInstanceType:SoundInstanceTypeInboundRingtone];
return soundInstance;
}
+(SoundInstance*) instanceOfOutboundRingtone {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Outbound];
+ (SoundInstance *)instanceOfOutboundRingtone {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Outbound];
[soundInstance setAudioToLoopIndefinitely];
[soundInstance setInstanceType:SoundInstanceTypeOutboundRingtone];
return soundInstance;
}
+(SoundInstance*) instanceOfHandshakeSound {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Handshake];
+ (SoundInstance *)instanceOfHandshakeSound {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Handshake];
[soundInstance setAudioToLoopIndefinitely];
[soundInstance setInstanceType:SoundInstanceTypeHandshakeSound];
return soundInstance;
}
+(SoundInstance*) instanceOfCompletedSound {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Completed];
+ (SoundInstance *)instanceOfCompletedSound {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Completed];
[soundInstance setInstanceType:SoundInstanceTypeCompletedSound];
return soundInstance;
}
+(SoundInstance*) instanceOfBusySound {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Busy];
+ (SoundInstance *)instanceOfBusySound {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Busy];
[soundInstance setAudioLoopCount:10];
[soundInstance setInstanceType:SoundInstanceTypeBusySound];
return soundInstance;
}
+(SoundInstance*) instanceOfErrorAlert {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Failure];
+ (SoundInstance *)instanceOfErrorAlert {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Failure];
[soundInstance setInstanceType:SoundInstanceTypeErrorAlert];
return soundInstance;
}
+(SoundInstance*) instanceOfAlert {
SoundInstance* soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Alert];
+ (SoundInstance *)instanceOfAlert {
SoundInstance *soundInstance = [SoundInstance soundInstanceForFile:SoundFile_Alert];
[soundInstance setInstanceType:SoundInstanceTypeAlert];
return soundInstance;
}
@end

View File

@ -19,13 +19,12 @@ typedef enum {
@property (nonatomic) SoundInstanceType instanceType;
+(SoundInstance*) soundInstanceForFile:(NSString*) audioFile;
-(NSString*) getId;
+ (SoundInstance *)soundInstanceForFile:(NSString *)audioFile;
- (NSString *)getId;
-(void) setAudioToLoopIndefinitely;
-(void) setAudioLoopCount:(NSInteger) loopCount;
-(void) setCompeletionBlock:(void (^)(SoundInstance*)) block;
- (void)setAudioToLoopIndefinitely;
- (void)setAudioLoopCount:(NSInteger)loopCount;
- (void)setCompeletionBlock:(void (^)(SoundInstance *))block;
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag;
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
@end

View File

@ -1,7 +1,7 @@
#import "SoundInstance.h"
@interface SoundInstance (){
void (^completionBlock)(SoundInstance*);
@interface SoundInstance () {
void (^completionBlock)(SoundInstance *);
}
@property (retain) AVAudioPlayer *audioPlayer;
@ -10,31 +10,31 @@
@implementation SoundInstance
+(SoundInstance*) soundInstanceForFile:(NSString*) audioFile {
SoundInstance* soundInstance = [SoundInstance new];
soundInstance.audioPlayer = [soundInstance.class createAudioPlayerForFile:audioFile];
+ (SoundInstance *)soundInstanceForFile:(NSString *)audioFile {
SoundInstance *soundInstance = [SoundInstance new];
soundInstance.audioPlayer = [soundInstance.class createAudioPlayerForFile:audioFile];
[soundInstance.audioPlayer setDelegate:soundInstance];
return soundInstance;
}
-(NSString*) getId{
- (NSString *)getId {
return [[self.audioPlayer url] absoluteString];
}
-(void) play {
- (void)play {
[self.audioPlayer play];
}
-(void) stop {
- (void)stop {
[self.audioPlayer stop];
[self audioPlayerDidFinishPlaying:self.audioPlayer successfully:YES];
}
-(void) setAudioToLoopIndefinitely {
- (void)setAudioToLoopIndefinitely {
self.audioPlayer.numberOfLoops = -1;
}
-(void) setAudioLoopCount:(NSInteger) loopCount {
- (void)setAudioLoopCount:(NSInteger)loopCount {
self.audioPlayer.numberOfLoops = loopCount;
}
@ -45,25 +45,26 @@
return _instanceType;
}
+(NSURL*) urlToFile:(NSString*) file {
return [NSURL fileURLWithPath:
[NSString stringWithFormat:@"%@/%@", NSBundle.mainBundle.resourcePath,file]];
+ (NSURL *)urlToFile:(NSString *)file {
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", NSBundle.mainBundle.resourcePath, file]];
}
+(AVAudioPlayer*) createAudioPlayerForFile:(NSString*) audioFile {
NSURL* url = [self urlToFile:audioFile];
+ (AVAudioPlayer *)createAudioPlayerForFile:(NSString *)audioFile {
NSURL *url = [self urlToFile:audioFile];
NSError *error;
AVAudioPlayer* audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (nil == audioPlayer){ NSLog(@" %@",[error description]);}
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (nil == audioPlayer) {
NSLog(@" %@", [error description]);
}
return audioPlayer;
}
-(void) setCompeletionBlock:(void (^)(SoundInstance* )) block {
- (void)setCompeletionBlock:(void (^)(SoundInstance *))block {
completionBlock = block;
}
-(void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
completionBlock(self);
}
@end

View File

@ -2,7 +2,7 @@
#import "SoundInstance.h"
/**
* SoundPlayer tracks and controls all Audiofiles being played. Currently only one instance
* SoundPlayer tracks and controls all Audiofiles being played. Currently only one instance
* of a given sound can be played at a given time. Attemping to play multiple intances of a
* sound is ignored. Multiple different sound instances can be played concurrently.
*/
@ -13,11 +13,11 @@
@property (strong, nonatomic) id<SoundPlayerDelegate> delegate;
-(void) playSound:(SoundInstance*) player;
-(void) stopSound:(SoundInstance*) player;
- (void)playSound:(SoundInstance *)player;
- (void)stopSound:(SoundInstance *)player;
-(void) stopAllAudio;
-(void) awake;
- (void)stopAllAudio;
- (void)awake;
@end

View File

@ -1,15 +1,15 @@
#import "SoundPlayer.h"
@interface SoundInstance ()
-(void) play;
-(void) stop;
- (void)play;
- (void)stop;
@end
@implementation SoundPlayer
NSMutableDictionary* currentActiveAudioPlayers;
NSMutableDictionary *currentActiveAudioPlayers;
-(SoundPlayer*) init{
- (SoundPlayer *)init {
currentActiveAudioPlayers = [NSMutableDictionary dictionary];
return self;
}
@ -17,52 +17,52 @@ NSMutableDictionary* currentActiveAudioPlayers;
#pragma mark Delegate Implementations
-(void) addSoundToManifest:(SoundInstance*) sound {
@synchronized(currentActiveAudioPlayers){
[sound setCompeletionBlock:^(SoundInstance* soundInst) {
[self removeSoundFromManifest:soundInst];
if (self.delegate) {
[self.delegate didCompleteSoundInstanceOfType:soundInst.instanceType];
}
- (void)addSoundToManifest:(SoundInstance *)sound {
@synchronized(currentActiveAudioPlayers) {
[sound setCompeletionBlock:^(SoundInstance *soundInst) {
[self removeSoundFromManifest:soundInst];
if (self.delegate) {
[self.delegate didCompleteSoundInstanceOfType:soundInst.instanceType];
}
}];
[currentActiveAudioPlayers setValue:sound forKey:sound.getId];
}
}
-(void) removeSoundFromManifest:(SoundInstance*) sound {
- (void)removeSoundFromManifest:(SoundInstance *)sound {
[self removeSoundFromMainifestById:sound.getId];
}
-(void) removeSoundFromMainifestById:(NSString*) soundId {
@synchronized(currentActiveAudioPlayers){
- (void)removeSoundFromMainifestById:(NSString *)soundId {
@synchronized(currentActiveAudioPlayers) {
[currentActiveAudioPlayers removeObjectForKey:soundId];
}
}
-(void) playSound:(SoundInstance*) sound {
if (![self isSoundPlaying:sound]){
- (void)playSound:(SoundInstance *)sound {
if (![self isSoundPlaying:sound]) {
[self addSoundToManifest:sound];
[sound play];
}
}
-(void) stopSound:(SoundInstance*) sound {
SoundInstance* playingSoundInstance = currentActiveAudioPlayers[sound.getId];
- (void)stopSound:(SoundInstance *)sound {
SoundInstance *playingSoundInstance = currentActiveAudioPlayers[sound.getId];
[self removeSoundFromManifest:sound];
[playingSoundInstance stop];
}
-(void) stopAllAudio{
for( SoundInstance* sound in currentActiveAudioPlayers.allValues){
- (void)stopAllAudio {
for (SoundInstance *sound in currentActiveAudioPlayers.allValues) {
[self stopSound:sound];
}
}
-(BOOL) isSoundPlaying:(SoundInstance*) sound {
- (BOOL)isSoundPlaying:(SoundInstance *)sound {
return nil != currentActiveAudioPlayers[sound.getId];
}
-(void) awake {
for( SoundInstance* sound in currentActiveAudioPlayers.allValues){
- (void)awake {
for (SoundInstance *sound in currentActiveAudioPlayers.allValues) {
[sound play];
}
}

View File

@ -1,7 +1,7 @@
#import <Foundation/Foundation.h>
#import "Queue.h"
#import "EncodedAudioFrame.h"
#import "EncodedAudioPacket.h"
#import "Queue.h"
#define AUDIO_FRAMES_PER_PACKET 2
@ -18,17 +18,20 @@
*
*/
@interface AudioPacker : NSObject {
@private NSMutableArray* framesToSend;
@private uint16_t nextSequenceNumber;
@private Queue* audioFrameToReceiveQueue;
@private
NSMutableArray *framesToSend;
@private
uint16_t nextSequenceNumber;
@private
Queue *audioFrameToReceiveQueue;
}
+(AudioPacker*) audioPacker;
+ (AudioPacker *)audioPacker;
-(void)packFrame:(EncodedAudioFrame*)frame;
-(EncodedAudioPacket*) tryGetFinishedAudioPacket;
- (void)packFrame:(EncodedAudioFrame *)frame;
- (EncodedAudioPacket *)tryGetFinishedAudioPacket;
-(void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket*)potentiallyMissingPacket;
-(EncodedAudioFrame*) tryGetReceivedFrame;
- (void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket *)potentiallyMissingPacket;
- (EncodedAudioFrame *)tryGetReceivedFrame;
@end

View File

@ -1,42 +1,43 @@
#import "AudioPacker.h"
#import "CryptoTools.h"
#import "Conversions.h"
#import "CryptoTools.h"
#import "Util.h"
@implementation AudioPacker
+(AudioPacker*) audioPacker {
AudioPacker* newAudioPackerInstance = [AudioPacker new];
+ (AudioPacker *)audioPacker {
AudioPacker *newAudioPackerInstance = [AudioPacker new];
newAudioPackerInstance->audioFrameToReceiveQueue = [Queue new];
newAudioPackerInstance->framesToSend = [NSMutableArray array];
newAudioPackerInstance->nextSequenceNumber = [CryptoTools generateSecureRandomUInt16];
newAudioPackerInstance->framesToSend = [NSMutableArray array];
newAudioPackerInstance->nextSequenceNumber = [CryptoTools generateSecureRandomUInt16];
// interop fix:
// cut off the high bit (the sign bit), to avoid confusion over signed-ness when peer receives the initial number
// also cut off the next bit, so that at least 2^14 packets (instead of 1) must fail to arrive before confusion can be caused
// also cut off the next bit, so that at least 2^14 packets (instead of 1) must fail to arrive before confusion can
// be caused
newAudioPackerInstance->nextSequenceNumber &= 0x3FFF;
return newAudioPackerInstance;
}
-(void)packFrame:(EncodedAudioFrame*)frame{
require(frame != nil);
require(!frame.isMissingAudioData);
- (void)packFrame:(EncodedAudioFrame *)frame {
ows_require(frame != nil);
ows_require(!frame.isMissingAudioData);
[framesToSend addObject:frame.tryGetAudioData];
}
-(EncodedAudioPacket*) tryGetFinishedAudioPacket{
if (framesToSend.count < AUDIO_FRAMES_PER_PACKET) return nil;
- (EncodedAudioPacket *)tryGetFinishedAudioPacket {
if (framesToSend.count < AUDIO_FRAMES_PER_PACKET)
return nil;
uint16_t sequenceNumber = nextSequenceNumber++;
NSData* payload = [framesToSend ows_concatDatas];
NSData *payload = [framesToSend ows_concatDatas];
[framesToSend removeAllObjects];
return [EncodedAudioPacket encodedAudioPacketWithAudioData:payload
andSequenceNumber:sequenceNumber];
return [EncodedAudioPacket encodedAudioPacketWithAudioData:payload andSequenceNumber:sequenceNumber];
}
-(void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket*)potentiallyMissingPacket{
- (void)unpackPotentiallyMissingAudioPacket:(EncodedAudioPacket *)potentiallyMissingPacket {
if (potentiallyMissingPacket != nil) {
[self enqueueFramesForPacket:potentiallyMissingPacket];
} else {
@ -44,26 +45,26 @@
}
}
-(EncodedAudioFrame*) tryGetReceivedFrame{
- (EncodedAudioFrame *)tryGetReceivedFrame {
return [audioFrameToReceiveQueue tryDequeue];
}
#pragma mark -
#pragma mark Private Methods
-(void) enqueueFramesForPacket:(EncodedAudioPacket*)packet {
require(packet != nil);
NSData* audioData = [packet audioData];
- (void)enqueueFramesForPacket:(EncodedAudioPacket *)packet {
ows_require(packet != nil);
NSData *audioData = [packet audioData];
requireState(audioData.length % AUDIO_FRAMES_PER_PACKET == 0);
NSUInteger frameSize = audioData.length / AUDIO_FRAMES_PER_PACKET;
for (NSUInteger i = 0; i < AUDIO_FRAMES_PER_PACKET; i++) {
NSData* frameData = [audioData subdataWithRange:NSMakeRange(frameSize*i, frameSize)];
NSData *frameData = [audioData subdataWithRange:NSMakeRange(frameSize * i, frameSize)];
[audioFrameToReceiveQueue enqueue:[EncodedAudioFrame encodedAudioFrameWithData:frameData]];
}
}
-(void) enqueueFramesForMissingPacket {
- (void)enqueueFramesForMissingPacket {
for (NSUInteger i = 0; i < AUDIO_FRAMES_PER_PACKET; i++) {
[audioFrameToReceiveQueue enqueue:[EncodedAudioFrame encodedAudioFrameWithoutData]];
}

View File

@ -9,12 +9,14 @@
*
**/
@interface AudioSocket : NSObject {
@private SrtpSocket* srtpSocket;
@private bool started;
@private
SrtpSocket *srtpSocket;
@private
bool started;
}
+(AudioSocket*) audioSocketOver:(SrtpSocket*)srtpSocket;
-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(TOCCancelToken*)untilCancelledToken;
-(void) send:(EncodedAudioPacket*)audioPacket;
+ (AudioSocket *)audioSocketOver:(SrtpSocket *)srtpSocket;
- (void)startWithHandler:(PacketHandler *)handler untilCancelled:(TOCCancelToken *)untilCancelledToken;
- (void)send:(EncodedAudioPacket *)audioPacket;
@end

View File

@ -2,33 +2,32 @@
@implementation AudioSocket
+(AudioSocket*) audioSocketOver:(SrtpSocket*)srtpSocket {
require(srtpSocket != nil);
AudioSocket* p = [AudioSocket new];
p->srtpSocket = srtpSocket;
+ (AudioSocket *)audioSocketOver:(SrtpSocket *)srtpSocket {
ows_require(srtpSocket != nil);
AudioSocket *p = [AudioSocket new];
p->srtpSocket = srtpSocket;
return p;
}
-(void) startWithHandler:(PacketHandler*)handler untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(handler != nil);
- (void)startWithHandler:(PacketHandler *)handler untilCancelled:(TOCCancelToken *)untilCancelledToken {
ows_require(handler != nil);
requireState(!started);
started = true;
PacketHandlerBlock valueHandler = ^(RtpPacket* rtpPacket) {
require(rtpPacket != nil);
require([rtpPacket isKindOfClass:[RtpPacket class]]);
[handler handlePacket:[EncodedAudioPacket encodedAudioPacketWithAudioData:[rtpPacket payload]
andSequenceNumber:[rtpPacket sequenceNumber]]];
PacketHandlerBlock valueHandler = ^(RtpPacket *rtpPacket) {
ows_require(rtpPacket != nil);
ows_require([rtpPacket isKindOfClass:[RtpPacket class]]);
[handler handlePacket:[EncodedAudioPacket encodedAudioPacketWithAudioData:[rtpPacket payload]
andSequenceNumber:[rtpPacket sequenceNumber]]];
};
[srtpSocket startWithHandler:[PacketHandler packetHandler:valueHandler
withErrorHandler:[handler errorHandler]]
[srtpSocket startWithHandler:[PacketHandler packetHandler:valueHandler withErrorHandler:[handler errorHandler]]
untilCancelled:untilCancelledToken];
}
-(void) send:(EncodedAudioPacket*)audioPacket {
require(audioPacket != nil);
RtpPacket* rtpPacket = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:[audioPacket sequenceNumber]
- (void)send:(EncodedAudioPacket *)audioPacket {
ows_require(audioPacket != nil);
RtpPacket *rtpPacket = [RtpPacket rtpPacketWithDefaultsAndSequenceNumber:[audioPacket sequenceNumber]
andPayload:[audioPacket audioData]];
[srtpSocket secureAndSendRtpPacket:rtpPacket];
}

View File

@ -7,22 +7,28 @@
/**
*
* CallAudioManager is responsible for creating and managing components related to playing real time audio communicated over a network.
* CallAudioManager is responsible for creating and managing components related to playing real time audio communicated
*over a network.
*
* The components are for playing/recording, processing, and transporting audio data.
*
**/
@interface CallAudioManager : NSObject<AudioCallbackHandler> {
@private RemoteIOAudio* audioInterface;
@private AudioProcessor* audioProcessor;
@private AudioSocket* audioSocket;
@private bool started;
@private NSUInteger bytesInPlaybackBuffer;
@interface CallAudioManager : NSObject <AudioCallbackHandler> {
@private
RemoteIOAudio *audioInterface;
@private
AudioProcessor *audioProcessor;
@private
AudioSocket *audioSocket;
@private
bool started;
@private
NSUInteger bytesInPlaybackBuffer;
}
+(CallAudioManager*) callAudioManagerStartedWithAudioSocket:(AudioSocket*)audioSocket
andErrorHandler:(ErrorHandlerBlock)errorHandler
untilCancelled:(TOCCancelToken*)untilCancelledToken;
-(BOOL) toggleMute;
+ (CallAudioManager *)callAudioManagerStartedWithAudioSocket:(AudioSocket *)audioSocket
andErrorHandler:(ErrorHandlerBlock)errorHandler
untilCancelled:(TOCCancelToken *)untilCancelledToken;
- (BOOL)toggleMute;
@end

View File

@ -5,63 +5,64 @@
@implementation CallAudioManager
+(CallAudioManager*) callAudioManagerStartedWithAudioSocket:(AudioSocket*)audioSocket
andErrorHandler:(ErrorHandlerBlock)errorHandler
untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(audioSocket != nil);
AudioProcessor* processor = [AudioProcessor audioProcessor];
CallAudioManager* newCallAudioManagerInstance = [CallAudioManager new];
newCallAudioManagerInstance->audioProcessor = processor;
newCallAudioManagerInstance->audioSocket = audioSocket;
+ (CallAudioManager *)callAudioManagerStartedWithAudioSocket:(AudioSocket *)audioSocket
andErrorHandler:(ErrorHandlerBlock)errorHandler
untilCancelled:(TOCCancelToken *)untilCancelledToken {
ows_require(audioSocket != nil);
AudioProcessor *processor = [AudioProcessor audioProcessor];
CallAudioManager *newCallAudioManagerInstance = [CallAudioManager new];
newCallAudioManagerInstance->audioProcessor = processor;
newCallAudioManagerInstance->audioSocket = audioSocket;
[newCallAudioManagerInstance startWithErrorHandler:errorHandler untilCancelled:untilCancelledToken];
return newCallAudioManagerInstance;
}
-(void) startWithErrorHandler:(ErrorHandlerBlock)errorHandler untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(errorHandler != nil);
require(untilCancelledToken != nil);
- (void)startWithErrorHandler:(ErrorHandlerBlock)errorHandler untilCancelled:(TOCCancelToken *)untilCancelledToken {
ows_require(errorHandler != nil);
ows_require(untilCancelledToken != nil);
@synchronized(self) {
requireState(!started);
started = true;
if (untilCancelledToken.isAlreadyCancelled) return;
if (untilCancelledToken.isAlreadyCancelled)
return;
audioInterface = [RemoteIOAudio remoteIOInterfaceStartedWithDelegate:self untilCancelled:untilCancelledToken];
PacketHandlerBlock handler = ^(EncodedAudioPacket* packet) {
[audioProcessor receivedPacket:packet];
PacketHandlerBlock handler = ^(EncodedAudioPacket *packet) {
[audioProcessor receivedPacket:packet];
};
[audioSocket startWithHandler:[PacketHandler packetHandler:handler
withErrorHandler:errorHandler]
[audioSocket startWithHandler:[PacketHandler packetHandler:handler withErrorHandler:errorHandler]
untilCancelled:untilCancelledToken];
}
}
-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining {
- (void)handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining {
if (bytesInPlaybackBuffer >= requested) {
bytesInPlaybackBuffer -= requested;
}
NSUInteger bytesAddedIfPullMore = [audioProcessor.codec decodedFrameSizeInBytes];
double minSafeBufferSize = MAX(requested, bytesAddedIfPullMore)*SAFETY_FACTOR_FOR_COMPUTE_DELAY;
double minSafeBufferSize = MAX(requested, bytesAddedIfPullMore) * SAFETY_FACTOR_FOR_COMPUTE_DELAY;
while (bytesInPlaybackBuffer < minSafeBufferSize) {
NSData* decodedAudioData = [audioProcessor tryDecodeOrInferFrame];
if (decodedAudioData == nil) break;
NSData *decodedAudioData = [audioProcessor tryDecodeOrInferFrame];
if (decodedAudioData == nil)
break;
[audioInterface populatePlaybackQueueWithData:decodedAudioData];
bytesInPlaybackBuffer += decodedAudioData.length;
}
}
-(void) handleNewDataRecorded:(CyclicalBuffer*)recordingQueue {
NSArray* encodedPackets = [audioProcessor encodeAudioPacketsFromBuffer:recordingQueue];
for (EncodedAudioPacket* packet in encodedPackets) {
- (void)handleNewDataRecorded:(CyclicalBuffer *)recordingQueue {
NSArray *encodedPackets = [audioProcessor encodeAudioPacketsFromBuffer:recordingQueue];
for (EncodedAudioPacket *packet in encodedPackets) {
[audioSocket send:packet];
}
}
-(BOOL) toggleMute {
return [audioInterface toggleMute];
- (BOOL)toggleMute {
return [audioInterface toggleMute];
}
@end

View File

@ -8,13 +8,14 @@
**/
@interface EncodedAudioFrame : NSObject {
@private NSData* audioData;
@private
NSData *audioData;
}
+(EncodedAudioFrame*) encodedAudioFrameWithData:(NSData*)audioData;
+(EncodedAudioFrame*) encodedAudioFrameWithoutData;
+ (EncodedAudioFrame *)encodedAudioFrameWithData:(NSData *)audioData;
+ (EncodedAudioFrame *)encodedAudioFrameWithoutData;
-(bool) isMissingAudioData;
-(NSData*) tryGetAudioData;
- (bool)isMissingAudioData;
- (NSData *)tryGetAudioData;
@end

View File

@ -1,22 +1,22 @@
#import "EncodedAudioFrame.h"
#import "Constraints.h"
#import "EncodedAudioFrame.h"
@implementation EncodedAudioFrame
+(EncodedAudioFrame*) encodedAudioFrameWithData:(NSData*)audioData {
require(audioData != nil);
EncodedAudioFrame* frame = [EncodedAudioFrame new];
frame->audioData = audioData;
+ (EncodedAudioFrame *)encodedAudioFrameWithData:(NSData *)audioData {
ows_require(audioData != nil);
EncodedAudioFrame *frame = [EncodedAudioFrame new];
frame->audioData = audioData;
return frame;
}
+(EncodedAudioFrame*) encodedAudioFrameWithoutData {
+ (EncodedAudioFrame *)encodedAudioFrameWithoutData {
return [EncodedAudioFrame new];
}
-(bool) isMissingAudioData {
- (bool)isMissingAudioData {
return audioData == nil;
}
-(NSData*) tryGetAudioData {
- (NSData *)tryGetAudioData {
return audioData;
}

View File

@ -11,9 +11,9 @@
**/
@interface EncodedAudioPacket : NSObject
@property (readonly,nonatomic) NSData* audioData;
@property (readonly,nonatomic) uint16_t sequenceNumber;
@property (readonly, nonatomic) NSData *audioData;
@property (readonly, nonatomic) uint16_t sequenceNumber;
+(EncodedAudioPacket*) encodedAudioPacketWithAudioData:(NSData*)audioData andSequenceNumber:(uint16_t)sequenceNumber;
+ (EncodedAudioPacket *)encodedAudioPacketWithAudioData:(NSData *)audioData andSequenceNumber:(uint16_t)sequenceNumber;
@end

View File

@ -1,15 +1,15 @@
#import "EncodedAudioPacket.h"
#import "Constraints.h"
#import "EncodedAudioPacket.h"
@implementation EncodedAudioPacket
@synthesize audioData, sequenceNumber;
+(EncodedAudioPacket*) encodedAudioPacketWithAudioData:(NSData*)audioData andSequenceNumber:(uint16_t)sequenceNumber {
require(audioData != nil);
EncodedAudioPacket* p = [EncodedAudioPacket new];
p->audioData = audioData;
p->sequenceNumber = sequenceNumber;
+ (EncodedAudioPacket *)encodedAudioPacketWithAudioData:(NSData *)audioData andSequenceNumber:(uint16_t)sequenceNumber {
ows_require(audioData != nil);
EncodedAudioPacket *p = [EncodedAudioPacket new];
p->audioData = audioData;
p->sequenceNumber = sequenceNumber;
return p;
}

View File

@ -1,21 +1,17 @@
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <Foundation/Foundation.h>
#import "AudioCallbackHandler.h"
#import "CyclicalBuffer.h"
#import "Environment.h"
#import "RemoteIOBufferListWrapper.h"
#import "Terminable.h"
enum State {
NOT_STARTED,
STARTED,
TERMINATED
};
enum State { NOT_STARTED, STARTED, TERMINATED };
/**
*
* RemoteIOAudio is responsible for playing audio through the speakers and
* RemoteIOAudio is responsible for playing audio through the speakers and
* recording audio through the microphone. It sends/receives this information
* to/from its AudioCallbackHandler delegate.
*
@ -25,30 +21,29 @@ enum State {
*
*/
@interface RemoteIOAudio : NSObject <AVAudioSessionDelegate> {
AudioUnit rioAudioUnit;
BOOL isStreaming;
id<AudioCallbackHandler> delegate;
NSMutableSet * unusedBuffers;
id<OccurrenceLogger> starveLogger;
id<ConditionLogger> conditionLogger;
id<ValueLogger> playbackBufferSizeLogger;
id<ValueLogger> recordingQueueSizeLogger;
AudioUnit rioAudioUnit;
BOOL isStreaming;
id<AudioCallbackHandler> delegate;
NSMutableSet *unusedBuffers;
id<OccurrenceLogger> starveLogger;
id<ConditionLogger> conditionLogger;
id<ValueLogger> playbackBufferSizeLogger;
id<ValueLogger> recordingQueueSizeLogger;
}
@property (nonatomic,readonly) enum State state;
@property (strong) CyclicalBuffer* recordingQueue;
@property (strong) CyclicalBuffer* playbackQueue;
@property (assign) AudioUnit rioAudioUnit;
@property (nonatomic, readonly) enum State state;
@property (strong) CyclicalBuffer *recordingQueue;
@property (strong) CyclicalBuffer *playbackQueue;
@property (assign) AudioUnit rioAudioUnit;
+(RemoteIOAudio*) remoteIOInterfaceStartedWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(TOCCancelToken*)untilCancelledToken;
-(void) populatePlaybackQueueWithData:(NSData*)data;
-(NSUInteger)getSampleRateInHertz;
-(BOOL) toggleMute;
+ (RemoteIOAudio *)remoteIOInterfaceStartedWithDelegate:(id<AudioCallbackHandler>)delegateIn
untilCancelled:(TOCCancelToken *)untilCancelledToken;
- (void)populatePlaybackQueueWithData:(NSData *)data;
- (NSUInteger)getSampleRateInHertz;
- (BOOL)toggleMute;
@end

View File

@ -1,6 +1,6 @@
#import "AppAudioManager.h"
#import "Environment.h"
#import "Constraints.h"
#import "Environment.h"
#import "RemoteIOAudio.h"
#import "ThreadManager.h"
#import "Util.h"
@ -12,8 +12,8 @@
#define BUFFER_SIZE 8000
#define FLAG_MUTED 1
#define FLAG_UNMUTED 0
#define FLAG_MUTED 1
#define FLAG_UNMUTED 0
@implementation RemoteIOAudio
@ -21,32 +21,37 @@
static bool doesActiveInstanceExist;
+(RemoteIOAudio*) remoteIOInterfaceStartedWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(TOCCancelToken*)untilCancelledToken {
checkOperationDescribe(!doesActiveInstanceExist, @"Only one RemoteIOInterfance instance can exist at a time. Adding more will break previous instances.");
+ (RemoteIOAudio *)remoteIOInterfaceStartedWithDelegate:(id<AudioCallbackHandler>)delegateIn
untilCancelled:(TOCCancelToken *)untilCancelledToken {
checkOperationDescribe(
!doesActiveInstanceExist,
@"Only one RemoteIOInterfance instance can exist at a time. Adding more will break previous instances.");
doesActiveInstanceExist = true;
RemoteIOAudio* newRemoteIoInterface = [RemoteIOAudio new];
newRemoteIoInterface->starveLogger = [Environment.logging getOccurrenceLoggerForSender:newRemoteIoInterface withKey:@"starve"];
RemoteIOAudio *newRemoteIoInterface = [RemoteIOAudio new];
newRemoteIoInterface->starveLogger =
[Environment.logging getOccurrenceLoggerForSender:newRemoteIoInterface withKey:@"starve"];
newRemoteIoInterface->conditionLogger = [Environment.logging getConditionLoggerForSender:newRemoteIoInterface];
newRemoteIoInterface->recordingQueue = [CyclicalBuffer new];
newRemoteIoInterface->playbackQueue = [CyclicalBuffer new];
newRemoteIoInterface->unusedBuffers = [NSMutableSet set];
newRemoteIoInterface->state = NOT_STARTED;
newRemoteIoInterface->playbackBufferSizeLogger = [Environment.logging getValueLoggerForValue:@"|playback queue|" from:newRemoteIoInterface];
newRemoteIoInterface->recordingQueueSizeLogger = [Environment.logging getValueLoggerForValue:@"|recording queue|" from:newRemoteIoInterface];
newRemoteIoInterface->recordingQueue = [CyclicalBuffer new];
newRemoteIoInterface->playbackQueue = [CyclicalBuffer new];
newRemoteIoInterface->unusedBuffers = [NSMutableSet set];
newRemoteIoInterface->state = NOT_STARTED;
newRemoteIoInterface->playbackBufferSizeLogger =
[Environment.logging getValueLoggerForValue:@"|playback queue|" from:newRemoteIoInterface];
newRemoteIoInterface->recordingQueueSizeLogger =
[Environment.logging getValueLoggerForValue:@"|recording queue|" from:newRemoteIoInterface];
while (newRemoteIoInterface->unusedBuffers.count < INITIAL_NUMBER_OF_BUFFERS) {
[newRemoteIoInterface addUnusedBuffer];
}
[newRemoteIoInterface setupAudio];
[newRemoteIoInterface startWithDelegate:delegateIn untilCancelled:untilCancelledToken];
return newRemoteIoInterface;
}
-(void)setupAudio {
- (void)setupAudio {
[AppAudioManager.sharedInstance requestRecordingPrivilege];
rioAudioUnit = [self makeAudioUnit];
[self setAudioEnabled];
@ -56,15 +61,15 @@ static bool doesActiveInstanceExist;
[self checkDone:AudioUnitInitialize(rioAudioUnit)];
[[AppAudioManager sharedInstance] updateAudioRouter];
}
-(AudioUnit)makeAudioUnit {
- (AudioUnit)makeAudioUnit {
AudioComponentDescription audioUnitDescription = [self makeAudioComponentDescription];
AudioComponent component = AudioComponentFindNext(NULL, &audioUnitDescription);
AudioComponent component = AudioComponentFindNext(NULL, &audioUnitDescription);
AudioUnit unit;
[self checkDone:AudioComponentInstanceNew(component, &unit)];
return unit;
}
-(AudioComponentDescription) makeAudioComponentDescription {
- (AudioComponentDescription)makeAudioComponentDescription {
AudioComponentDescription d;
d.componentType = kAudioUnitType_Output;
d.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
@ -73,7 +78,7 @@ static bool doesActiveInstanceExist;
d.componentFlagsMask = 0;
return d;
}
-(void)setAudioEnabled {
- (void)setAudioEnabled {
const UInt32 enable = 1;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioOutputUnitProperty_EnableIO,
@ -88,7 +93,7 @@ static bool doesActiveInstanceExist;
&enable,
sizeof(enable))];
}
-(void)setAudioStreamFormat {
- (void)setAudioStreamFormat {
const AudioStreamBasicDescription streamDesc = [self makeAudioStreamBasicDescription];
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_StreamFormat,
@ -103,7 +108,7 @@ static bool doesActiveInstanceExist;
&streamDesc,
sizeof(streamDesc))];
}
-(AudioStreamBasicDescription) makeAudioStreamBasicDescription {
- (AudioStreamBasicDescription)makeAudioStreamBasicDescription {
const UInt32 framesPerPacket = 1;
AudioStreamBasicDescription d;
d.mSampleRate = SAMPLE_RATE;
@ -112,14 +117,12 @@ static bool doesActiveInstanceExist;
d.mChannelsPerFrame = 1;
d.mBitsPerChannel = 16;
d.mBytesPerPacket = SAMPLE_SIZE_IN_BYTES;
d.mBytesPerFrame = framesPerPacket*SAMPLE_SIZE_IN_BYTES;
d.mBytesPerFrame = framesPerPacket * SAMPLE_SIZE_IN_BYTES;
d.mReserved = 0;
d.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked;
d.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
return d;
}
-(void)setAudioCallbacks {
- (void)setAudioCallbacks {
const AURenderCallbackStruct recordingCallbackStruct = {recordingCallback, (__bridge void *)(self)};
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioOutputUnitProperty_SetInputCallback,
@ -127,7 +130,7 @@ static bool doesActiveInstanceExist;
INPUT_BUS,
&recordingCallbackStruct,
sizeof(recordingCallbackStruct))];
const AURenderCallbackStruct playbackCallbackStruct = {playbackCallback, (__bridge void *)(self)};
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_SetRenderCallback,
@ -136,7 +139,7 @@ static bool doesActiveInstanceExist;
&playbackCallbackStruct,
sizeof(playbackCallbackStruct))];
}
-(void)unsetAudioShouldAllocateBuffer {
- (void)unsetAudioShouldAllocateBuffer {
const UInt32 shouldAllocateBuffer = 0;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
@ -146,41 +149,43 @@ static bool doesActiveInstanceExist;
sizeof(shouldAllocateBuffer))];
}
-(RemoteIOBufferListWrapper*) addUnusedBuffer {
RemoteIOBufferListWrapper* buf = [RemoteIOBufferListWrapper remoteIOBufferListWithMonoBufferSize:BUFFER_SIZE];
- (RemoteIOBufferListWrapper *)addUnusedBuffer {
RemoteIOBufferListWrapper *buf = [RemoteIOBufferListWrapper remoteIOBufferListWithMonoBufferSize:BUFFER_SIZE];
[unusedBuffers addObject:buf];
return buf;
}
-(RemoteIOBufferListWrapper*) tryTakeUnusedBuffer {
RemoteIOBufferListWrapper* buffer = (RemoteIOBufferListWrapper*)[unusedBuffers anyObject];
if (buffer == nil) return nil;
- (RemoteIOBufferListWrapper *)tryTakeUnusedBuffer {
RemoteIOBufferListWrapper *buffer = (RemoteIOBufferListWrapper *)[unusedBuffers anyObject];
if (buffer == nil)
return nil;
[unusedBuffers removeObject:buffer];
return buffer;
}
-(void) returnUsedBuffer:(RemoteIOBufferListWrapper*)buffer {
require(buffer != nil);
if (state == TERMINATED) return; // in case a buffer was in use as termination occurred
- (void)returnUsedBuffer:(RemoteIOBufferListWrapper *)buffer {
ows_require(buffer != nil);
if (state == TERMINATED)
return; // in case a buffer was in use as termination occurred
[unusedBuffers addObject:buffer];
}
-(void) startWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(delegateIn != nil);
@synchronized(self){
- (void)startWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(TOCCancelToken *)untilCancelledToken {
ows_require(delegateIn != nil);
@synchronized(self) {
requireState(state == NOT_STARTED);
delegate = delegateIn;
[self checkDone:AudioOutputUnitStart(rioAudioUnit)];
state = STARTED;
}
[untilCancelledToken whenCancelledDo:^{
@synchronized(self) {
state = TERMINATED;
doesActiveInstanceExist = false;
[self checkDone:AudioOutputUnitStop(rioAudioUnit)];
[AppAudioManager.sharedInstance releaseRecordingPrivilege];
[unusedBuffers removeAllObjects];
}
@synchronized(self) {
state = TERMINATED;
doesActiveInstanceExist = false;
[self checkDone:AudioOutputUnitStop(rioAudioUnit)];
[AppAudioManager.sharedInstance releaseRecordingPrivilege];
[unusedBuffers removeAllObjects];
}
}];
}
@ -190,13 +195,10 @@ static OSStatus recordingCallback(void *inRefCon,
UInt32 inBusNumber,
UInt32 inNumberSamples,
AudioBufferList *ioData) {
@autoreleasepool {
RemoteIOAudio *instance = (__bridge RemoteIOAudio*)inRefCon;
RemoteIOBufferListWrapper* buffer;
RemoteIOAudio *instance = (__bridge RemoteIOAudio *)inRefCon;
RemoteIOBufferListWrapper *buffer;
@synchronized(instance) {
buffer = [instance tryTakeUnusedBuffer];
}
@ -212,33 +214,34 @@ static OSStatus recordingCallback(void *inRefCon,
inNumberSamples,
&bufferList)];
buffer.sampleCount = inNumberSamples;
[instance performSelector:@selector(onRecordedDataIntoBuffer:)
onThread:[ThreadManager lowLatencyThread]
withObject:buffer
waitUntilDone:NO];
}
return noErr;
}
-(void) onRecordedDataIntoBuffer:(RemoteIOBufferListWrapper*)buffer {
@synchronized(self){
if (state == TERMINATED) return;
NSData* recordedAudioVolatile = [NSData dataWithBytesNoCopy:[buffer audioBufferList]->mBuffers[0].mData
length:[buffer sampleCount]*SAMPLE_SIZE_IN_BYTES
- (void)onRecordedDataIntoBuffer:(RemoteIOBufferListWrapper *)buffer {
@synchronized(self) {
if (state == TERMINATED)
return;
NSData *recordedAudioVolatile = [NSData dataWithBytesNoCopy:[buffer audioBufferList]->mBuffers[0].mData
length:[buffer sampleCount] * SAMPLE_SIZE_IN_BYTES
freeWhenDone:NO];
[recordingQueue enqueueData:recordedAudioVolatile];
[self returnUsedBuffer:buffer];
}
[recordingQueueSizeLogger logValue:[recordingQueue enqueuedLength]];
[delegate handleNewDataRecorded:recordingQueue];
}
-(void)populatePlaybackQueueWithData:(NSData*)data {
require(data != nil);
if (data.length == 0) return;
@synchronized(self){
- (void)populatePlaybackQueueWithData:(NSData *)data {
ows_require(data != nil);
if (data.length == 0)
return;
@synchronized(self) {
[playbackQueue enqueueData:data];
}
}
@ -248,55 +251,60 @@ static OSStatus playbackCallback(void *inRefCon,
UInt32 inBusNumber,
UInt32 inNumberSamples,
AudioBufferList *ioData) {
RemoteIOAudio* instance = (__bridge RemoteIOAudio*)inRefCon;
RemoteIOAudio *instance = (__bridge RemoteIOAudio *)inRefCon;
NSUInteger requestedByteCount = inNumberSamples * SAMPLE_SIZE_IN_BYTES;
NSUInteger availableByteCount;
@synchronized(instance) {
availableByteCount = [[instance playbackQueue] enqueuedLength];
if (availableByteCount < requestedByteCount) {
NSUInteger starveAmount = requestedByteCount - availableByteCount;
[instance->starveLogger markOccurrence:@(starveAmount)];
} else {
NSData* audioToCopyVolatile = [[instance playbackQueue] dequeuePotentialyVolatileDataWithLength:requestedByteCount];
NSData *audioToCopyVolatile =
[[instance playbackQueue] dequeuePotentialyVolatileDataWithLength:requestedByteCount];
memcpy(ioData->mBuffers[0].mData, [audioToCopyVolatile bytes], audioToCopyVolatile.length);
}
}
[Operation asyncRun:^{[instance onRequestedPlaybackDataAmount:requestedByteCount
andHadAvailableAmount:availableByteCount];}
[Operation asyncRun:^{
[instance onRequestedPlaybackDataAmount:requestedByteCount andHadAvailableAmount:availableByteCount];
}
onThread:[ThreadManager lowLatencyThread]];
if (availableByteCount < requestedByteCount) {
return 1; // arbitrary error code
}
return noErr;
}
-(void) onRequestedPlaybackDataAmount:(NSUInteger)requestedByteCount andHadAvailableAmount:(NSUInteger)availableByteCount {
- (void)onRequestedPlaybackDataAmount:(NSUInteger)requestedByteCount
andHadAvailableAmount:(NSUInteger)availableByteCount {
@synchronized(self) {
if (state == TERMINATED) return;
if (state == TERMINATED)
return;
}
NSUInteger consumedByteCount = availableByteCount >= requestedByteCount ? requestedByteCount : 0;
NSUInteger consumedByteCount = availableByteCount >= requestedByteCount ? requestedByteCount : 0;
NSUInteger remainingByteCount = availableByteCount - consumedByteCount;
[playbackBufferSizeLogger logValue:remainingByteCount];
[delegate handlePlaybackOccurredWithBytesRequested:requestedByteCount andBytesRemaining:remainingByteCount];
}
-(void) dealloc{
- (void)dealloc {
if (state != TERMINATED) {
doesActiveInstanceExist = false;
}
}
-(NSUInteger)getSampleRateInHertz {
- (NSUInteger)getSampleRateInHertz {
return SAMPLE_RATE;
}
-(void)checkDone:(OSStatus)resultCode {
if (resultCode == kAudioSessionNoError) return;
NSString* failure;
- (void)checkDone:(OSStatus)resultCode {
if (resultCode == kAudioSessionNoError)
return;
NSString *failure;
if (resultCode == kAudioServicesUnsupportedPropertyError) {
failure = @"unsupportedPropertyError";
} else if (resultCode == kAudioServicesBadPropertySizeError) {
@ -307,38 +315,38 @@ static OSStatus playbackCallback(void *inRefCon,
failure = @"systemSoundUnspecifiedError";
} else if (resultCode == kAudioServicesSystemSoundClientTimedOutError) {
failure = @"systemSoundClientTimedOutError";
} else if (resultCode == errSecParam){
} else if (resultCode == errSecParam) {
failure = @"oneOrMoreNonValidParameter";
}else {
} else {
failure = [@(resultCode) description];
}
[conditionLogger logError:[NSString stringWithFormat:@"StatusCheck failed: %@", failure]];
}
-(bool) isAudioMuted {
UInt32 currentMuteFlag;
UInt32 propertyByteSize;
[self checkDone:AudioUnitGetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
- (bool)isAudioMuted {
UInt32 currentMuteFlag;
UInt32 propertyByteSize;
[self checkDone:AudioUnitGetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
OUTPUT_BUS,
&currentMuteFlag,
&propertyByteSize)];
return (FLAG_MUTED == currentMuteFlag);
&propertyByteSize)];
return (FLAG_MUTED == currentMuteFlag);
}
-(BOOL) toggleMute {
BOOL shouldBeMuted = !self.isAudioMuted;
UInt32 newValue = shouldBeMuted ? FLAG_MUTED : FLAG_UNMUTED;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
- (BOOL)toggleMute {
BOOL shouldBeMuted = !self.isAudioMuted;
UInt32 newValue = shouldBeMuted ? FLAG_MUTED : FLAG_UNMUTED;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
OUTPUT_BUS,
&newValue,
sizeof(newValue))];
return shouldBeMuted;
sizeof(newValue))];
return shouldBeMuted;
}

View File

@ -1,6 +1,6 @@
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <Foundation/Foundation.h>
/**
*
@ -10,9 +10,9 @@
@interface RemoteIOBufferListWrapper : NSObject
@property (nonatomic, assign) NSUInteger sampleCount;
@property (nonatomic, readonly) AudioBufferList* audioBufferList;
@property (nonatomic, readonly) AudioBufferList *audioBufferList;
+(RemoteIOBufferListWrapper*) remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize;
+ (RemoteIOBufferListWrapper *)remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize;
@end

View File

@ -4,18 +4,18 @@
@synthesize sampleCount, audioBufferList;
+(RemoteIOBufferListWrapper*) remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize {
AudioBufferList* audioBufferList = malloc(sizeof(AudioBufferList));
audioBufferList->mNumberBuffers = 1;
+ (RemoteIOBufferListWrapper *)remoteIOBufferListWithMonoBufferSize:(NSUInteger)bufferSize {
AudioBufferList *audioBufferList = malloc(sizeof(AudioBufferList));
audioBufferList->mNumberBuffers = 1;
audioBufferList->mBuffers[0].mNumberChannels = 1;
audioBufferList->mBuffers[0].mDataByteSize = (UInt32)bufferSize;
audioBufferList->mBuffers[0].mData = malloc(bufferSize);
RemoteIOBufferListWrapper* w = [RemoteIOBufferListWrapper new];
w->audioBufferList = audioBufferList;
audioBufferList->mBuffers[0].mDataByteSize = (UInt32)bufferSize;
audioBufferList->mBuffers[0].mData = malloc(bufferSize);
RemoteIOBufferListWrapper *w = [RemoteIOBufferListWrapper new];
w->audioBufferList = audioBufferList;
return w;
}
-(void) dealloc {
- (void)dealloc {
free(audioBufferList->mBuffers[0].mData);
}

View File

@ -9,29 +9,28 @@
*
**/
@interface SpeexCodec : NSObject {
void* decodingState;
void *decodingState;
SpeexBits decodingBits;
spx_int16_t decodingFrameSize;
spx_int16_t* decodingBuffer;
void* encodingState;
spx_int16_t *decodingBuffer;
void *encodingState;
SpeexBits encodingBits;
spx_int16_t encodingFrameSize;
NSUInteger encodingBufferSizeInBytes;
BOOL terminated;
id<ConditionLogger> logging;
NSUInteger cachedEncodedLength;
}
+(SpeexCodec*) speexCodec;
+(NSUInteger)frameSizeInSamples;
-(NSUInteger)encodedFrameSizeInBytes;
-(NSUInteger)decodedFrameSizeInBytes;
+ (SpeexCodec *)speexCodec;
+ (NSUInteger)frameSizeInSamples;
- (NSUInteger)encodedFrameSizeInBytes;
- (NSUInteger)decodedFrameSizeInBytes;
-(NSData*)decode:(NSData*)encodedData;
-(NSData*)encode:(NSData*)rawData;
- (NSData *)decode:(NSData *)encodedData;
- (NSData *)encode:(NSData *)rawData;
@end

View File

@ -1,5 +1,5 @@
#import "Environment.h"
#import "Constraints.h"
#import "Environment.h"
#import "SpeexCodec.h"
@implementation SpeexCodec
@ -13,62 +13,64 @@
#define QUALITY_PARAMETER 3
#define COMPLEXITY_PARAMETER 1
+(SpeexCodec*) speexCodec {
SpeexCodec* c = [SpeexCodec new];
c->logging = [Environment.logging getConditionLoggerForSender:self];
+ (SpeexCodec *)speexCodec {
SpeexCodec *c = [SpeexCodec new];
c->logging = [Environment.logging getConditionLoggerForSender:self];
[c openSpeex];
return c;
}
-(void) dealloc {
- (void)dealloc {
[self closeSpeex];
}
+(NSUInteger)frameSizeInSamples {
+ (NSUInteger)frameSizeInSamples {
return FRAME_SIZE_IN_SAMPLES;
}
-(NSUInteger)encodedFrameSizeInBytes {
- (NSUInteger)encodedFrameSizeInBytes {
requireState(cachedEncodedLength != 0);
return cachedEncodedLength;
}
-(NSUInteger)decodedFrameSizeInBytes {
return FRAME_SIZE_IN_SAMPLES*DECODED_SAMPLE_SIZE_IN_BYTES;
- (NSUInteger)decodedFrameSizeInBytes {
return FRAME_SIZE_IN_SAMPLES * DECODED_SAMPLE_SIZE_IN_BYTES;
}
-(void) determineDecodedLength {
NSData* encoded = [self encode:[NSMutableData dataWithLength:[self decodedFrameSizeInBytes]]];
- (void)determineDecodedLength {
NSData *encoded = [self encode:[NSMutableData dataWithLength:[self decodedFrameSizeInBytes]]];
cachedEncodedLength = encoded.length;
}
-(NSData*)encode:(NSData*)rawData {
require(rawData != nil);
require(rawData.length == FRAME_SIZE_IN_SAMPLES*DECODED_SAMPLE_SIZE_IN_BYTES);
- (NSData *)encode:(NSData *)rawData {
ows_require(rawData != nil);
ows_require(rawData.length == FRAME_SIZE_IN_SAMPLES * DECODED_SAMPLE_SIZE_IN_BYTES);
speex_bits_reset(&encodingBits);
speex_encode_int(encodingState, (spx_int16_t*)[rawData bytes], &encodingBits);
NSMutableData* outputBuffer = [NSMutableData dataWithLength:encodingBufferSizeInBytes];
int outputSizeInBytes = speex_bits_write(&encodingBits, [outputBuffer mutableBytes], (int)encodingBufferSizeInBytes);
speex_encode_int(encodingState, (spx_int16_t *)[rawData bytes], &encodingBits);
NSMutableData *outputBuffer = [NSMutableData dataWithLength:encodingBufferSizeInBytes];
int outputSizeInBytes =
speex_bits_write(&encodingBits, [outputBuffer mutableBytes], (int)encodingBufferSizeInBytes);
checkOperation(outputSizeInBytes > 0);
[outputBuffer setLength:(NSUInteger)outputSizeInBytes];
return outputBuffer;
}
-(NSData*)decode:(NSData*)potentiallyMissingEncodedData {
- (NSData *)decode:(NSData *)potentiallyMissingEncodedData {
NSUInteger encodedDataLength = potentiallyMissingEncodedData.length;
if (potentiallyMissingEncodedData == nil) {
encodedDataLength = [self decodedFrameSizeInBytes]; // size for infering audio data
}
if(encodedDataLength == 0) return nil;
if (encodedDataLength == 0)
return nil;
SpeexBits *dbits = [self getSpeexBitsFromData:potentiallyMissingEncodedData andDataLength:(int)encodedDataLength];
int decodedLength = [self decodeSpeexBits:dbits withLength:(int)encodedDataLength];
return [NSData dataWithBytes:decodingBuffer length:(NSUInteger)decodedLength*sizeof(spx_int16_t)];
return [NSData dataWithBytes:decodingBuffer length:(NSUInteger)decodedLength * sizeof(spx_int16_t)];
}
-(NSUInteger) encodedDataLengthFromData:(NSData*)potentiallyMissingEncodedData{
- (NSUInteger)encodedDataLengthFromData:(NSData *)potentiallyMissingEncodedData {
if (potentiallyMissingEncodedData != nil) {
return potentiallyMissingEncodedData.length;
}
@ -76,10 +78,10 @@
}
- (SpeexBits *)getSpeexBitsFromData:(NSData *)encodedData andDataLength:(int)encodedDataLength {
SpeexBits *dbits=NULL;
char* encodingStream;
if([encodedData bytes] != NULL){
encodingStream = (char*)[encodedData bytes];
SpeexBits *dbits = NULL;
char *encodingStream;
if ([encodedData bytes] != NULL) {
encodingStream = (char *)[encodedData bytes];
speex_bits_read_from(&decodingBits, encodingStream, encodedDataLength);
dbits = &decodingBits;
}
@ -87,28 +89,34 @@
}
- (int)decodeSpeexBits:(SpeexBits *)dbits withLength:(int)encodedDataLength {
int decodingBufferIndex = 0;
int decodingBufferIndex = 0;
int decodingBufferLength = (int)[self decodedFrameSizeInBytes];
int count = 0;
while(0 == speex_decode_int(decodingState, dbits, decodingBuffer + decodingBufferIndex)){
int count = 0;
while (0 == speex_decode_int(decodingState, dbits, decodingBuffer + decodingBufferIndex)) {
count++;
decodingBufferIndex += decodingFrameSize;
if(decodingBufferIndex + decodingFrameSize > decodingBufferLength){
[logging logWarning:[NSString stringWithFormat:@"out of space in the decArr buffer, idx=%d, frameSize=%d, length=%d", decodingBufferIndex,decodingFrameSize,decodingBufferLength]];
if (decodingBufferIndex + decodingFrameSize > decodingBufferLength) {
[logging
logWarning:[NSString
stringWithFormat:@"out of space in the decArr buffer, idx=%d, frameSize=%d, length=%d",
decodingBufferIndex,
decodingFrameSize,
decodingBufferLength]];
break;
}
if(decodingBufferIndex + decodingFrameSize > decodingFrameSize * MAX_FRAMES){
[logging logWarning:[NSString stringWithFormat:@"out of space in the dec_buffer buffer, idx=%d", decodingBufferIndex]];
if (decodingBufferIndex + decodingFrameSize > decodingFrameSize * MAX_FRAMES) {
[logging logWarning:[NSString stringWithFormat:@"out of space in the dec_buffer buffer, idx=%d",
decodingBufferIndex]];
break;
}
if(dbits == NULL){
if (dbits == NULL) {
break;
}
}
return decodingBufferIndex;
}
-(void) openSpeex {
- (void)openSpeex {
[self initiateEncoderAndDecoder];
[self applySpeexSettings];
[self initiateSpeexBuffers];
@ -116,28 +124,27 @@
}
- (void)initiateEncoderAndDecoder {
encodingState = speex_encoder_init(&speex_nb_mode);
decodingState = speex_decoder_init(&speex_nb_mode);
checkOperationDescribe(encodingState != NULL, @"speex encoder init failed");
checkOperationDescribe(decodingState != NULL, @"speex decoder init failed");
}
-(void)applySpeexSettings{
- (void)applySpeexSettings {
spx_int32_t tmp;
tmp=PERPETUAL_ENHANCER_PARAMETER;
tmp = PERPETUAL_ENHANCER_PARAMETER;
speex_decoder_ctl(decodingState, SPEEX_SET_ENH, &tmp);
tmp=VARIABLE_BIT_RATE_PARAMETER;
tmp = VARIABLE_BIT_RATE_PARAMETER;
speex_encoder_ctl(encodingState, SPEEX_SET_VBR, &tmp);
tmp=QUALITY_PARAMETER;
tmp = QUALITY_PARAMETER;
speex_encoder_ctl(encodingState, SPEEX_SET_QUALITY, &tmp);
tmp=COMPLEXITY_PARAMETER;
tmp = COMPLEXITY_PARAMETER;
speex_encoder_ctl(encodingState, SPEEX_SET_COMPLEXITY, &tmp);
speex_encoder_ctl(encodingState, SPEEX_GET_FRAME_SIZE, &encodingFrameSize);
speex_decoder_ctl(decodingState, SPEEX_GET_FRAME_SIZE, &decodingFrameSize);
int sampleRate = (int)SAMPLE_RATE;
speex_encoder_ctl(encodingState, SPEEX_SET_SAMPLING_RATE, &sampleRate);
speex_decoder_ctl(decodingState, SPEEX_SET_SAMPLING_RATE, &sampleRate);
@ -146,22 +153,22 @@
- (void)initiateSpeexBuffers {
speex_bits_init(&encodingBits);
speex_bits_init(&decodingBits);
encodingBufferSizeInBytes = (NSUInteger)encodingFrameSize * MAX_FRAMES;
decodingBuffer = (spx_int16_t *) malloc( sizeof( spx_int16_t ) * (NSUInteger)decodingFrameSize*MAX_FRAMES );
decodingBuffer = (spx_int16_t *)malloc(sizeof(spx_int16_t) * (NSUInteger)decodingFrameSize * MAX_FRAMES);
checkOperationDescribe(decodingBuffer != NULL, @"buffer allocation failed");
}
-(void) closeSpeex {
- (void)closeSpeex {
terminated = true;
speex_encoder_destroy( encodingState );
speex_decoder_destroy( decodingState );
speex_bits_destroy( &encodingBits );
speex_bits_destroy( &decodingBits );
speex_encoder_destroy(encodingState);
speex_decoder_destroy(decodingState);
speex_bits_destroy(&encodingBits);
speex_bits_destroy(&decodingBits);
free(decodingBuffer);
}

View File

@ -11,7 +11,7 @@
/**
*
* AudioProcessor is responsible for transforming audio as it travels between
* AudioProcessor is responsible for transforming audio as it travels between
* the network and the hardware.
*
* Processing involves:
@ -24,19 +24,24 @@
**/
@interface AudioProcessor : NSObject {
@private StretchFactorController* stretchFactorController;
@private AudioStretcher* audioStretcher;
@private AudioPacker* audioPacker;
@private JitterQueue* jitterQueue;
@private bool haveReceivedDataYet;
@private
StretchFactorController *stretchFactorController;
@private
AudioStretcher *audioStretcher;
@private
AudioPacker *audioPacker;
@private
JitterQueue *jitterQueue;
@private
bool haveReceivedDataYet;
}
@property (nonatomic,readonly) SpeexCodec* codec;
@property (nonatomic, readonly) SpeexCodec *codec;
+(AudioProcessor*) audioProcessor;
+ (AudioProcessor *)audioProcessor;
-(void) receivedPacket:(EncodedAudioPacket*)packet;
-(NSArray*) encodeAudioPacketsFromBuffer:(CyclicalBuffer*) buffer;
-(NSData*) tryDecodeOrInferFrame;
- (void)receivedPacket:(EncodedAudioPacket *)packet;
- (NSArray *)encodeAudioPacketsFromBuffer:(CyclicalBuffer *)buffer;
- (NSData *)tryDecodeOrInferFrame;
@end

View File

@ -4,56 +4,60 @@
@synthesize codec;
+(AudioProcessor*) audioProcessor {
JitterQueue* jitterQueue = [JitterQueue jitterQueue];
AudioProcessor* newAudioProcessorInstance = [AudioProcessor new];
newAudioProcessorInstance->codec = [SpeexCodec speexCodec];
newAudioProcessorInstance->stretchFactorController = [StretchFactorController stretchFactorControllerForJitterQueue:jitterQueue];
+ (AudioProcessor *)audioProcessor {
JitterQueue *jitterQueue = [JitterQueue jitterQueue];
AudioProcessor *newAudioProcessorInstance = [AudioProcessor new];
newAudioProcessorInstance->codec = [SpeexCodec speexCodec];
newAudioProcessorInstance->stretchFactorController =
[StretchFactorController stretchFactorControllerForJitterQueue:jitterQueue];
newAudioProcessorInstance->audioStretcher = [AudioStretcher audioStretcher];
newAudioProcessorInstance->jitterQueue = jitterQueue;
newAudioProcessorInstance->audioPacker = [AudioPacker audioPacker];
newAudioProcessorInstance->jitterQueue = jitterQueue;
newAudioProcessorInstance->audioPacker = [AudioPacker audioPacker];
return newAudioProcessorInstance;
}
-(void)receivedPacket:(EncodedAudioPacket *)packet {
- (void)receivedPacket:(EncodedAudioPacket *)packet {
[jitterQueue tryEnqueue:packet];
}
-(NSArray*) encodeAudioPacketsFromBuffer:(CyclicalBuffer*) buffer {
require(buffer != nil);
NSMutableArray* encodedFrames = [NSMutableArray array];
NSUInteger decodedFrameSize = [codec decodedFrameSizeInBytes];
while([buffer enqueuedLength] >= decodedFrameSize){
NSData* rawFrame = [buffer dequeueDataWithLength:decodedFrameSize];
- (NSArray *)encodeAudioPacketsFromBuffer:(CyclicalBuffer *)buffer {
ows_require(buffer != nil);
NSMutableArray *encodedFrames = [NSMutableArray array];
NSUInteger decodedFrameSize = [codec decodedFrameSizeInBytes];
while ([buffer enqueuedLength] >= decodedFrameSize) {
NSData *rawFrame = [buffer dequeueDataWithLength:decodedFrameSize];
requireState(rawFrame != nil);
NSData* encodedFrameData = [codec encode:rawFrame];
NSData *encodedFrameData = [codec encode:rawFrame];
[encodedFrames addObject:[EncodedAudioFrame encodedAudioFrameWithData:encodedFrameData]];
}
NSMutableArray* encodedPackets = [NSMutableArray array];
for (EncodedAudioFrame* frame in encodedFrames) {
NSMutableArray *encodedPackets = [NSMutableArray array];
for (EncodedAudioFrame *frame in encodedFrames) {
[audioPacker packFrame:frame];
EncodedAudioPacket* packet = [audioPacker tryGetFinishedAudioPacket];
if (packet != nil) [encodedPackets addObject:packet];
EncodedAudioPacket *packet = [audioPacker tryGetFinishedAudioPacket];
if (packet != nil)
[encodedPackets addObject:packet];
}
return encodedPackets;
}
-(EncodedAudioFrame*) pullFrame {
EncodedAudioFrame* frame = [audioPacker tryGetReceivedFrame];
if (frame != nil) return frame;
EncodedAudioPacket* potentiallyMissingPacket = [jitterQueue tryDequeue];
- (EncodedAudioFrame *)pullFrame {
EncodedAudioFrame *frame = [audioPacker tryGetReceivedFrame];
if (frame != nil)
return frame;
EncodedAudioPacket *potentiallyMissingPacket = [jitterQueue tryDequeue];
[audioPacker unpackPotentiallyMissingAudioPacket:potentiallyMissingPacket];
return [audioPacker tryGetReceivedFrame];
}
-(NSData*) tryDecodeOrInferFrame {
EncodedAudioFrame* frame = [self pullFrame];
- (NSData *)tryDecodeOrInferFrame {
EncodedAudioFrame *frame = [self pullFrame];
haveReceivedDataYet |= !frame.isMissingAudioData;
if (!haveReceivedDataYet) return nil;
NSData* raw = [codec decode:frame.tryGetAudioData];
if (!haveReceivedDataYet)
return nil;
NSData *raw = [codec decode:frame.tryGetAudioData];
double stretch = [stretchFactorController getAndUpdateDesiredStretchFactor];
return [audioStretcher stretchAudioData:raw stretchFactor:stretch];
}

View File

@ -12,10 +12,11 @@
**/
@interface AudioStretcher : NSObject {
@private struct time_scale_state_s timeScaleState;
@private
struct time_scale_state_s timeScaleState;
}
+(AudioStretcher*) audioStretcher;
-(NSData*) stretchAudioData:(NSData*)audioData stretchFactor:(double)stretchFactor;
+ (AudioStretcher *)audioStretcher;
- (NSData *)stretchAudioData:(NSData *)audioData stretchFactor:(double)stretchFactor;
@end

View File

@ -1,52 +1,55 @@
#import "AudioStretcher.h"
#import "Constraints.h"
#import "time_scale.h"
#import "Util.h"
#import "time_scale.h"
#define MIN_STRETCH_FACTOR 0.1
#define MAX_STRETCH_FACTOR 10
@implementation AudioStretcher
+(AudioStretcher*) audioStretcher {
AudioStretcher* s = [AudioStretcher new];
+ (AudioStretcher *)audioStretcher {
AudioStretcher *s = [AudioStretcher new];
checkOperation(time_scale_init(&s->timeScaleState, SAMPLE_RATE, 1.0) != NULL);
return s;
}
-(NSData*) stretchAudioData:(NSData*)audioData stretchFactor:(double)stretchFactor {
require(stretchFactor > MIN_STRETCH_FACTOR);
require(stretchFactor < MAX_STRETCH_FACTOR);
if (audioData == nil) return nil;
- (NSData *)stretchAudioData:(NSData *)audioData stretchFactor:(double)stretchFactor {
ows_require(stretchFactor > MIN_STRETCH_FACTOR);
ows_require(stretchFactor < MAX_STRETCH_FACTOR);
if (audioData == nil)
return nil;
checkOperationDescribe(time_scale_rate(&timeScaleState, (float)stretchFactor) == 0, @"Changing time scaling");
int inputSampleCount = (unsigned int)audioData.length/sizeof(int16_t);
int inputSampleCount = (unsigned int)audioData.length / sizeof(int16_t);
int maxOutputSampleCount = [self getSafeMaxOutputSampleCountFromInputSampleCount:inputSampleCount];
int16_t* input = (int16_t*)[audioData bytes];
NSMutableData* d = [NSMutableData dataWithLength:(NSUInteger)maxOutputSampleCount*sizeof(int16_t)];
int16_t *input = (int16_t *)[audioData bytes];
NSMutableData *d = [NSMutableData dataWithLength:(NSUInteger)maxOutputSampleCount * sizeof(int16_t)];
int outputSampleCount = time_scale(&timeScaleState, [d mutableBytes], input, inputSampleCount);
checkOperationDescribe(outputSampleCount >= 0 && outputSampleCount <= maxOutputSampleCount, @"Scaling audio");
return [d take:(NSUInteger)outputSampleCount*sizeof(int16_t)];
return [d take:(NSUInteger)outputSampleCount * sizeof(int16_t)];
}
-(int) getSafeMaxOutputSampleCountFromInputSampleCount:(int)inputSampleCount{
// WARNING: In some cases SpanDSP (time_scale.h v 1.20) underestimates how much buffer it will need, so we must pad the result to be safe.
- (int)getSafeMaxOutputSampleCountFromInputSampleCount:(int)inputSampleCount {
// WARNING: In some cases SpanDSP (time_scale.h v 1.20) underestimates how much buffer it will need, so we must pad
// the result to be safe.
// Issues has been notified upstream and once it is patched the padding can be removed
int unsafe_maxOutputSampleCount = time_scale_max_output_len(&timeScaleState, inputSampleCount);
const int BUFFER_OVERFLOW_PROTECTION_PAD = 2048;
int unsafe_maxOutputSampleCount = time_scale_max_output_len(&timeScaleState, inputSampleCount);
const int BUFFER_OVERFLOW_PROTECTION_PAD = 2048;
const int BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER = 2;
int expandedMaxCountToAvoidBufferOverflows = BUFFER_OVERFLOW_PROTECTION_PAD + (unsafe_maxOutputSampleCount * BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER);
int expandedMaxCountToAvoidBufferOverflows =
BUFFER_OVERFLOW_PROTECTION_PAD + (unsafe_maxOutputSampleCount * BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER);
checkOperation(expandedMaxCountToAvoidBufferOverflows >= 0);
return expandedMaxCountToAvoidBufferOverflows;
}
-(void) dealloc {
- (void)dealloc {
checkOperation(time_scale_release(&timeScaleState) == 0);
}

View File

@ -3,12 +3,13 @@
#import "DropoutTracker.h"
#import "Environment.h"
#import "JitterQueue.h"
#import "Terminable.h"
#import "SpeexCodec.h"
#import "Terminable.h"
/**
*
* DesiredBufferDepthController is used to determine how much audio should be kept in reserve, in case of network jitter.
* DesiredBufferDepthController is used to determine how much audio should be kept in reserve, in case of network
*jitter.
*
* An instance must be registered to receive notifications from the network jitter queue in order to function correctly.
* When packets arrive at a consistent rate without dropping, the desired buffer depth tends to decrease.
@ -16,13 +17,16 @@
*
**/
@interface DesiredBufferDepthController : NSObject<Terminable, JitterQueueNotificationReceiver> {
@private DropoutTracker* dropoutTracker;
@private DecayingSampleEstimator* decayingDesiredBufferDepth;
@private id<ValueLogger> desiredDelayLogger;
@interface DesiredBufferDepthController : NSObject <Terminable, JitterQueueNotificationReceiver> {
@private
DropoutTracker *dropoutTracker;
@private
DecayingSampleEstimator *decayingDesiredBufferDepth;
@private
id<ValueLogger> desiredDelayLogger;
}
+(DesiredBufferDepthController*) desiredBufferDepthControllerForJitterQueue:(JitterQueue*)jitterQueue;
-(double) getAndUpdateDesiredBufferDepth;
+ (DesiredBufferDepthController *)desiredBufferDepthControllerForJitterQueue:(JitterQueue *)jitterQueue;
- (double)getAndUpdateDesiredBufferDepth;
@end

View File

@ -1,7 +1,7 @@
#import "AudioPacker.h"
#import "DesiredBufferDepthController.h"
#import "PreferencesUtil.h"
#import "Util.h"
#import "AudioPacker.h"
#define MAX_DESIRED_FRAME_DELAY 12
#define MIN_DESIRED_FRAME_DELAY 0.5
@ -11,29 +11,29 @@
@implementation DesiredBufferDepthController
+(DesiredBufferDepthController*) desiredBufferDepthControllerForJitterQueue:(JitterQueue*)jitterQueue {
require(jitterQueue != nil);
NSTimeInterval audioDurationPerPacket = (NSTimeInterval)(AUDIO_FRAMES_PER_PACKET*[SpeexCodec frameSizeInSamples])
/ SAMPLE_RATE;
double initialDesiredBufferDepth = Environment.preferences.getCachedOrDefaultDesiredBufferDepth;
DropoutTracker* dropoutTracker = [DropoutTracker dropoutTrackerWithAudioDurationPerPacket:audioDurationPerPacket];
+ (DesiredBufferDepthController *)desiredBufferDepthControllerForJitterQueue:(JitterQueue *)jitterQueue {
ows_require(jitterQueue != nil);
DecayingSampleEstimator* decayingDesiredBufferDepth =
NSTimeInterval audioDurationPerPacket =
(NSTimeInterval)(AUDIO_FRAMES_PER_PACKET * [SpeexCodec frameSizeInSamples]) / SAMPLE_RATE;
double initialDesiredBufferDepth = Environment.preferences.getCachedOrDefaultDesiredBufferDepth;
DropoutTracker *dropoutTracker = [DropoutTracker dropoutTrackerWithAudioDurationPerPacket:audioDurationPerPacket];
DecayingSampleEstimator *decayingDesiredBufferDepth =
[DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:initialDesiredBufferDepth
andDecayPerUnitSample:DESIRED_BUFFER_DEPTH_DECAY_RATE];
DesiredBufferDepthController* result = [DesiredBufferDepthController new];
result->dropoutTracker = dropoutTracker;
result->decayingDesiredBufferDepth = decayingDesiredBufferDepth;
DesiredBufferDepthController *result = [DesiredBufferDepthController new];
result->dropoutTracker = dropoutTracker;
result->decayingDesiredBufferDepth = decayingDesiredBufferDepth;
result->desiredDelayLogger = [Environment.logging getValueLoggerForValue:@"desired buffer depth" from:self];
[jitterQueue registerWatcher:result];
return result;
}
-(double) getAndUpdateDesiredBufferDepth {
- (double)getAndUpdateDesiredBufferDepth {
double r = decayingDesiredBufferDepth.currentEstimate;
[decayingDesiredBufferDepth updateWithNextSample:[dropoutTracker getDepthForThreshold:DROPOUT_THRESHOLD]];
[decayingDesiredBufferDepth forceEstimateTo:[NumberUtil clamp:decayingDesiredBufferDepth.currentEstimate
@ -43,21 +43,23 @@
return r;
}
-(void) notifyArrival:(uint16_t)sequenceNumber {
- (void)notifyArrival:(uint16_t)sequenceNumber {
[dropoutTracker observeSequenceNumber:sequenceNumber];
}
-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType {
- (void)notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType {
}
-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type {
- (void)notifyBadDequeueOfType:(enum JitterBadDequeueType)type {
}
-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount {
- (void)notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount {
}
-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber {
- (void)notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber {
}
-(void) notifyDiscardOverflow:(uint16_t)sequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber {
- (void)notifyDiscardOverflow:(uint16_t)sequenceNumber
resyncingFrom:(uint16_t)oldReadHeadSequenceNumber
to:(uint16_t)newReadHeadSequenceNumber {
}
-(void) terminate {
- (void)terminate {
[Environment.preferences setCachedDesiredBufferDepth:decayingDesiredBufferDepth.currentEstimate];
}

View File

@ -28,23 +28,30 @@
*
* > @author Stuart O. Anderson
*
* Link to android implementation with comments:
* Link to android implementation with comments:
* https://github.com/WhisperSystems/RedPhone/blob/2a6e8cec64cc457d2eb02351d0f3adf769db7a84/src/org/thoughtcrime/redphone/audio/DropoutTracker.java
*
*/
@interface DropoutTracker : NSObject {
@private Queue* priorLatenesses;
@private NSMutableArray* lateBins;
@private SequenceCounter* sequenceCounter;
@private NSTimeInterval audioDurationPerPacket;
@private bool startTimeInitialized;
@private NSTimeInterval startTime;
@private NSTimeInterval drift;
@private
Queue *priorLatenesses;
@private
NSMutableArray *lateBins;
@private
SequenceCounter *sequenceCounter;
@private
NSTimeInterval audioDurationPerPacket;
@private
bool startTimeInitialized;
@private
NSTimeInterval startTime;
@private
NSTimeInterval drift;
}
+(DropoutTracker*) dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket;
-(void) observeSequenceNumber:(uint16_t)seqNum;
-(double)getDepthForThreshold:(NSUInteger)maxEvents;
+ (DropoutTracker *)dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket;
- (void)observeSequenceNumber:(uint16_t)seqNum;
- (double)getDepthForThreshold:(NSUInteger)maxEvents;
@end

View File

@ -9,14 +9,13 @@
@implementation DropoutTracker
+(DropoutTracker*) dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket {
DropoutTracker* d = [DropoutTracker new];
+ (DropoutTracker *)dropoutTrackerWithAudioDurationPerPacket:(NSTimeInterval)audioDurationPerPacket {
DropoutTracker *d = [DropoutTracker new];
d->audioDurationPerPacket = audioDurationPerPacket;
d->sequenceCounter = [SequenceCounter sequenceCounter];
d->priorLatenesses = [Queue new];
d->lateBins = [NSMutableArray array];
d->sequenceCounter = [SequenceCounter sequenceCounter];
d->priorLatenesses = [Queue new];
d->lateBins = [NSMutableArray array];
for (NSUInteger i = 0; i < PRIOR_LATENESS_LENGTH; i++) {
[d->priorLatenesses enqueue:@0.0];
@ -24,65 +23,64 @@
for (NSUInteger i = 0; i < LATE_BINS_LENGTH; i++) {
[d->lateBins addObject:[EventWindow eventWindowWithWindowDuration:LATE_BIN_WINDOW_IN_SECONDS]];
}
return d;
}
-(NSTimeInterval) detectPeak {
NSTimeInterval possiblePeakLatency = [[priorLatenesses peekAt:PRIOR_LATENESS_LENGTH/2] doubleValue];
for (NSUInteger i=0; i < PRIOR_LATENESS_LENGTH-1; i++) {
- (NSTimeInterval)detectPeak {
NSTimeInterval possiblePeakLatency = [[priorLatenesses peekAt:PRIOR_LATENESS_LENGTH / 2] doubleValue];
for (NSUInteger i = 0; i < PRIOR_LATENESS_LENGTH - 1; i++) {
if ([[priorLatenesses peekAt:i] doubleValue] > possiblePeakLatency) {
return -1;
}
}
return possiblePeakLatency;
}
-(void) observeSequenceNumber:(uint16_t)sequenceNumber {
- (void)observeSequenceNumber:(uint16_t)sequenceNumber {
int64_t expandedSequenceNumber = [sequenceCounter convertNext:sequenceNumber];
if( !startTimeInitialized ) {
startTime = [TimeUtil time];
if (!startTimeInitialized) {
startTime = [TimeUtil time];
startTimeInitialized = true;
}
NSTimeInterval expectedTime = startTime + drift + expandedSequenceNumber * audioDurationPerPacket;
NSTimeInterval now = [TimeUtil time];
NSTimeInterval secLate = now-expectedTime;
NSTimeInterval now = [TimeUtil time];
NSTimeInterval secLate = now - expectedTime;
[priorLatenesses enqueue:@(secLate)];
[priorLatenesses dequeue];
// update zero time
// if a packet arrives early, immediately update the timebase
// if it arrives late, conservatively update the timebase
drift += MIN(secLate, secLate / 50);
//Was the last packet a local peak?
// Was the last packet a local peak?
NSTimeInterval peakLatency = [self detectPeak];
if (peakLatency > 0) {
NSUInteger lateBin = (NSUInteger)[NumberUtil clamp:peakLatency/(audioDurationPerPacket / binsPerPacket)
NSUInteger lateBin = (NSUInteger)[NumberUtil clamp:peakLatency / (audioDurationPerPacket / binsPerPacket)
toMin:0
andMax:LATE_BINS_LENGTH - 1];
if (peakLatency <= maxActionableLatency) {
[lateBins[lateBin] addEventAtTime:now];
}
}
}
/// How many packets would we have needed to buffer to stay below the desired dropout event count
-(double)getDepthForThreshold:(NSUInteger)maxEvents {
- (double)getDepthForThreshold:(NSUInteger)maxEvents {
NSUInteger eventCount = 0;
NSTimeInterval now = [TimeUtil time];
NSTimeInterval now = [TimeUtil time];
for (NSUInteger depth = LATE_BINS_LENGTH; depth > 0; depth--) {
eventCount += [lateBins[depth-1] countAfterRemovingEventsBeforeWindowEndingAt:now];
eventCount += [lateBins[depth - 1] countAfterRemovingEventsBeforeWindowEndingAt:now];
if (eventCount > maxEvents) {
return (depth-1)/binsPerPacket;
return (depth - 1) / binsPerPacket;
}
}
return -1/binsPerPacket;
return -1 / binsPerPacket;
}
@end

View File

@ -1,38 +1,45 @@
#import <Foundation/Foundation.h>
#import "PriorityQueue.h"
#import "EncodedAudioPacket.h"
#import "Logging.h"
#import "JitterQueueNotificationReceiver.h"
#import "BufferDepthMeasure.h"
#import "EncodedAudioPacket.h"
#import "JitterQueueNotificationReceiver.h"
#import "Logging.h"
#import "PriorityQueue.h"
/**
*
* JitterQueue handles the details of organizing and consuming real-time data, which may fail to arrive on time or arrive out of order.
* JitterQueue handles the details of organizing and consuming real-time data, which may fail to arrive on time or
*arrive out of order.
*
**/
@interface JitterQueue : NSObject <BufferDepthMeasure> {
@private PriorityQueue* resultPriorityQueue;
@private uint16_t readHeadMin;
@private uint16_t readHeadSpan;
@private NSMutableSet* idsInJitterQueue;
@private NSMutableArray* watchers;
@private uint16_t largestLatestEnqueued;
@private
PriorityQueue *resultPriorityQueue;
@private
uint16_t readHeadMin;
@private
uint16_t readHeadSpan;
@private
NSMutableSet *idsInJitterQueue;
@private
NSMutableArray *watchers;
@private
uint16_t largestLatestEnqueued;
}
+(JitterQueue*) jitterQueue;
+ (JitterQueue *)jitterQueue;
-(void) registerWatcher:(id<JitterQueueNotificationReceiver>)watcher;
- (void)registerWatcher:(id<JitterQueueNotificationReceiver>)watcher;
// Provides a framed audio packet to be placed in sequence.
// Returns true if the packet was successfully enqueued.
// Returns false if the packet has arrived too late, far too early, or is a duplicate.
-(bool) tryEnqueue:(EncodedAudioPacket*)packet;
- (bool)tryEnqueue:(EncodedAudioPacket *)packet;
// Returns the next framed audio packet in sequence, or nil if the next packet has not arrived in time.
-(EncodedAudioPacket*) tryDequeue;
- (EncodedAudioPacket *)tryDequeue;
// The number of framed audio packets (contiguous or not) in the queue.
-(NSUInteger) count;
- (NSUInteger)count;
@end

View File

@ -1,6 +1,6 @@
#import "Environment.h"
#import "JitterQueue.h"
#import "Util.h"
#import "Environment.h"
#define TRANSITIVE_SAFETY_RANGE 0x4000
#define READ_HEAD_MAX_QUEUE_AHEAD 0x1000
@ -9,31 +9,32 @@
@implementation JitterQueue
+(JitterQueue*) jitterQueue {
JitterQueue* q = [JitterQueue new];
q->readHeadSpan = READ_HEAD_BAD_SPAN_THRESHOLD+1;
q->watchers = [NSMutableArray array];
+ (JitterQueue *)jitterQueue {
JitterQueue *q = [JitterQueue new];
q->readHeadSpan = READ_HEAD_BAD_SPAN_THRESHOLD + 1;
q->watchers = [NSMutableArray array];
[q registerWatcher:Environment.logging.jitterQueueNotificationReceiver];
return q;
}
-(void) registerWatcher:(id<JitterQueueNotificationReceiver>)watcher {
if (watcher == nil) return;
- (void)registerWatcher:(id<JitterQueueNotificationReceiver>)watcher {
if (watcher == nil)
return;
[watchers addObject:watcher];
}
-(NSUInteger) count {
- (NSUInteger)count {
return resultPriorityQueue.count;
}
-(bool) tryEnqueue:(EncodedAudioPacket*)audioPacket {
require(audioPacket != nil);
- (bool)tryEnqueue:(EncodedAudioPacket *)audioPacket {
ows_require(audioPacket != nil);
uint16_t sequenceNumber = [audioPacket sequenceNumber];
if (![self tryFitIntoSequence:sequenceNumber]) {
return false;
}
[idsInJitterQueue addObject:@(sequenceNumber)];
[resultPriorityQueue enqueue:audioPacket];
if ([NumberUtil congruentDifferenceMod2ToThe16From:largestLatestEnqueued to:sequenceNumber] > 0) {
@ -48,9 +49,10 @@
return true;
}
-(bool) tryFitIntoSequence:(uint16_t)sequenceNumber {
int16_t sequenceNumberRelativeToReadHead = [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin to:sequenceNumber];
- (bool)tryFitIntoSequence:(uint16_t)sequenceNumber {
int16_t sequenceNumberRelativeToReadHead =
[NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin to:sequenceNumber];
enum JitterBadArrivalType badArrivalType;
if ([self tryForceSyncIfNecessary:sequenceNumber]) {
return true;
@ -63,75 +65,73 @@
} else {
return true;
}
for (id<JitterQueueNotificationReceiver> watcher in watchers) {
[watcher notifyBadArrival:sequenceNumber ofType:badArrivalType];
}
return false;
}
-(bool) tryForceSyncIfNecessary:(uint16_t)sequenceNumber {
if (readHeadSpan <= READ_HEAD_BAD_SPAN_THRESHOLD) return false;
- (bool)tryForceSyncIfNecessary:(uint16_t)sequenceNumber {
if (readHeadSpan <= READ_HEAD_BAD_SPAN_THRESHOLD)
return false;
if (resultPriorityQueue != nil) { // (only log resyncs, not the initial sync)
for (id<JitterQueueNotificationReceiver> watcher in watchers) {
[watcher notifyResyncFrom:readHeadMin to:sequenceNumber];
}
}
readHeadMin = sequenceNumber;
readHeadMin = sequenceNumber;
largestLatestEnqueued = sequenceNumber;
readHeadSpan = 1;
idsInJitterQueue = [NSMutableSet set];
resultPriorityQueue = [JitterQueue makeCyclingPacketPriorityQueue];
readHeadSpan = 1;
idsInJitterQueue = [NSMutableSet set];
resultPriorityQueue = [JitterQueue makeCyclingPacketPriorityQueue];
return true;
}
+(PriorityQueue*) makeCyclingPacketPriorityQueue {
return [PriorityQueue priorityQueueAscendingWithComparator:^NSComparisonResult(EncodedAudioPacket* obj1, EncodedAudioPacket* obj2) {
int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:[obj2 sequenceNumber]
to:[obj1 sequenceNumber]];
requireState(abs(d) <= TRANSITIVE_SAFETY_RANGE);
return [NumberUtil signOfInt32:d];
}];
+ (PriorityQueue *)makeCyclingPacketPriorityQueue {
return [PriorityQueue
priorityQueueAscendingWithComparator:^NSComparisonResult(EncodedAudioPacket *obj1, EncodedAudioPacket *obj2) {
int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:[obj2 sequenceNumber] to:[obj1 sequenceNumber]];
requireState(abs(d) <= TRANSITIVE_SAFETY_RANGE);
return [NumberUtil signOfInt32:d];
}];
}
-(void) discardExcess {
if (resultPriorityQueue.count <= MAXIMUM_JITTER_QUEUE_SIZE_BEFORE_DISCARDING) return;
EncodedAudioPacket* discarded = [resultPriorityQueue dequeue];
- (void)discardExcess {
if (resultPriorityQueue.count <= MAXIMUM_JITTER_QUEUE_SIZE_BEFORE_DISCARDING)
return;
EncodedAudioPacket *discarded = [resultPriorityQueue dequeue];
uint16_t discardedSequenceNumber = [discarded sequenceNumber];
[idsInJitterQueue removeObject:@(discardedSequenceNumber)];
uint16_t oldReadHeadMax = readHeadMin+readHeadSpan-1;
readHeadMin = [resultPriorityQueue.peek sequenceNumber];
readHeadSpan = 1;
uint16_t oldReadHeadMax = readHeadMin + readHeadSpan - 1;
readHeadMin = [resultPriorityQueue.peek sequenceNumber];
readHeadSpan = 1;
for (id<JitterQueueNotificationReceiver> e in watchers) {
[e notifyDiscardOverflow:discardedSequenceNumber
resyncingFrom:oldReadHeadMax
to:readHeadMin];
[e notifyDiscardOverflow:discardedSequenceNumber resyncingFrom:oldReadHeadMax to:readHeadMin];
}
}
-(EncodedAudioPacket*) tryDequeue {
if ([self checkReactIfOutOfSyncForDequeue]
|| [self checkReactIfEmptyForDequeue]
|| [self checkReactIfNoDataUnderReadHeadForDequeue]) {
- (EncodedAudioPacket *)tryDequeue {
if ([self checkReactIfOutOfSyncForDequeue] || [self checkReactIfEmptyForDequeue] ||
[self checkReactIfNoDataUnderReadHeadForDequeue]) {
return nil;
}
EncodedAudioPacket* result = [resultPriorityQueue dequeue];
readHeadMin = [result sequenceNumber]+1;
readHeadSpan = 1;
EncodedAudioPacket *result = [resultPriorityQueue dequeue];
readHeadMin = [result sequenceNumber] + 1;
readHeadSpan = 1;
[idsInJitterQueue removeObject:@([result sequenceNumber])];
for (id<JitterQueueNotificationReceiver> e in watchers) {
[e notifyDequeue:[result sequenceNumber] withRemainingEnqueuedItemCount:idsInJitterQueue.count];
}
return result;
}
-(bool) checkReactIfOutOfSyncForDequeue {
- (bool)checkReactIfOutOfSyncForDequeue {
bool isOutOfSync = readHeadSpan > READ_HEAD_BAD_SPAN_THRESHOLD;
if (isOutOfSync) {
for (id<JitterQueueNotificationReceiver> watcher in watchers) {
@ -140,7 +140,7 @@
}
return isOutOfSync;
}
-(bool) checkReactIfEmptyForDequeue {
- (bool)checkReactIfEmptyForDequeue {
bool isEmpty = resultPriorityQueue.count == 0;
if (isEmpty) {
readHeadSpan += 1;
@ -150,11 +150,10 @@
}
return isEmpty;
}
-(bool) checkReactIfNoDataUnderReadHeadForDequeue {
EncodedAudioPacket* result = resultPriorityQueue.peek;
int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin
to:[result sequenceNumber]];
bool notUnderHead = d < 0 || d >= readHeadSpan;
- (bool)checkReactIfNoDataUnderReadHeadForDequeue {
EncodedAudioPacket *result = resultPriorityQueue.peek;
int16_t d = [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin to:[result sequenceNumber]];
bool notUnderHead = d < 0 || d >= readHeadSpan;
if (notUnderHead) {
readHeadSpan += 1;
for (id<JitterQueueNotificationReceiver> watcher in watchers) {
@ -164,8 +163,9 @@
return notUnderHead;
}
-(int16_t) currentBufferDepth {
if (readHeadSpan > READ_HEAD_BAD_SPAN_THRESHOLD) return 0;
- (int16_t)currentBufferDepth {
if (readHeadSpan > READ_HEAD_BAD_SPAN_THRESHOLD)
return 0;
return [NumberUtil congruentDifferenceMod2ToThe16From:readHeadMin + readHeadSpan - 1 to:largestLatestEnqueued];
}

View File

@ -12,15 +12,20 @@
**/
@interface StretchFactorController : NSObject {
@private int currentStretchMode;
@private id<BufferDepthMeasure> bufferDepthMeasure;
@private DesiredBufferDepthController* desiredBufferDepthController;
@private DecayingSampleEstimator* decayingBufferDepthMeasure;
@private id<ValueLogger> stretchModeChangeLogger;
@private
int currentStretchMode;
@private
id<BufferDepthMeasure> bufferDepthMeasure;
@private
DesiredBufferDepthController *desiredBufferDepthController;
@private
DecayingSampleEstimator *decayingBufferDepthMeasure;
@private
id<ValueLogger> stretchModeChangeLogger;
}
+(StretchFactorController*) stretchFactorControllerForJitterQueue:(JitterQueue*)jitterQueue;
+ (StretchFactorController *)stretchFactorControllerForJitterQueue:(JitterQueue *)jitterQueue;
-(double) getAndUpdateDesiredStretchFactor;
- (double)getAndUpdateDesiredStretchFactor;
@end

View File

@ -5,7 +5,7 @@
#define STRETCH_MODE_NORMAL 1
#define STRETCH_MODE_SHRINK 2
#define STRETCH_MODE_SUPER_SHRINK 3
static double STRETCH_MODE_FACTORS[] = {1/0.95, 1, 1/1.05, 0.5};
static double STRETCH_MODE_FACTORS[] = {1 / 0.95, 1, 1 / 1.05, 0.5};
#define SUPER_SHRINK_THRESHOLD 10
#define SHRINK_THRESHOLD 3.0
@ -15,55 +15,61 @@ static double STRETCH_MODE_FACTORS[] = {1/0.95, 1, 1/1.05, 0.5};
@implementation StretchFactorController
+(StretchFactorController*) stretchFactorControllerForJitterQueue:(JitterQueue*)jitterQueue {
require(jitterQueue != nil);
DesiredBufferDepthController* desiredBufferDepthController =
+ (StretchFactorController *)stretchFactorControllerForJitterQueue:(JitterQueue *)jitterQueue {
ows_require(jitterQueue != nil);
DesiredBufferDepthController *desiredBufferDepthController =
[DesiredBufferDepthController desiredBufferDepthControllerForJitterQueue:jitterQueue];
StretchFactorController* p = [StretchFactorController new];
StretchFactorController *p = [StretchFactorController new];
p->desiredBufferDepthController = desiredBufferDepthController;
p->currentStretchMode = STRETCH_MODE_NORMAL;
p->bufferDepthMeasure = jitterQueue;
p->decayingBufferDepthMeasure = [DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0 andDecayPerUnitSample:BUFFER_DEPTH_DECAYING_FACTOR];
p->currentStretchMode = STRETCH_MODE_NORMAL;
p->bufferDepthMeasure = jitterQueue;
p->decayingBufferDepthMeasure =
[DecayingSampleEstimator decayingSampleEstimatorWithInitialEstimate:0
andDecayPerUnitSample:BUFFER_DEPTH_DECAYING_FACTOR];
p->stretchModeChangeLogger = [Environment.logging getValueLoggerForValue:@"stretch factor" from:self];
return p;
}
-(int) reconsiderStretchMode {
- (int)reconsiderStretchMode {
int16_t currentBufferDepth = bufferDepthMeasure.currentBufferDepth;
[decayingBufferDepthMeasure updateWithNextSample:currentBufferDepth];
double desiredBufferDepth = desiredBufferDepthController.getAndUpdateDesiredBufferDepth;
double currentBufferDepthDelta = currentBufferDepth - desiredBufferDepth;
double currentBufferDepthDelta = currentBufferDepth - desiredBufferDepth;
double decayingBufferDepthDelta = decayingBufferDepthMeasure.currentEstimate - desiredBufferDepth;
bool shouldStartSuperShrink = currentBufferDepthDelta > SUPER_SHRINK_THRESHOLD;
bool shouldStartSuperShrink = currentBufferDepthDelta > SUPER_SHRINK_THRESHOLD;
bool shouldMaintainSuperShrink = currentBufferDepthDelta > 0 && currentStretchMode == STRETCH_MODE_SUPER_SHRINK;
bool shouldEndSuperShrinkAndResetEstimate = !shouldMaintainSuperShrink && currentStretchMode == STRETCH_MODE_SUPER_SHRINK;
bool shouldStartShrink = decayingBufferDepthDelta > SHRINK_THRESHOLD;
bool shouldEndSuperShrinkAndResetEstimate =
!shouldMaintainSuperShrink && currentStretchMode == STRETCH_MODE_SUPER_SHRINK;
bool shouldStartShrink = decayingBufferDepthDelta > SHRINK_THRESHOLD;
bool shouldMaintainShrink = decayingBufferDepthDelta > 0 && currentStretchMode == STRETCH_MODE_SHRINK;
bool shouldStartExpand = decayingBufferDepthDelta < EXPAND_THRESHOLD;
bool shouldStartExpand = decayingBufferDepthDelta < EXPAND_THRESHOLD;
bool shouldMaintainExpand = decayingBufferDepthDelta < 0 && currentStretchMode == STRETCH_MODE_EXPAND;
if (shouldEndSuperShrinkAndResetEstimate) {
[decayingBufferDepthMeasure forceEstimateTo:desiredBufferDepth];
return STRETCH_MODE_NORMAL;
}
if (shouldStartSuperShrink) return STRETCH_MODE_SUPER_SHRINK;
if (shouldStartShrink) return STRETCH_MODE_SHRINK;
if (shouldStartExpand) return STRETCH_MODE_EXPAND;
if (shouldMaintainShrink || shouldMaintainExpand || shouldMaintainSuperShrink) return currentStretchMode;
if (shouldStartSuperShrink)
return STRETCH_MODE_SUPER_SHRINK;
if (shouldStartShrink)
return STRETCH_MODE_SHRINK;
if (shouldStartExpand)
return STRETCH_MODE_EXPAND;
if (shouldMaintainShrink || shouldMaintainExpand || shouldMaintainSuperShrink)
return currentStretchMode;
return STRETCH_MODE_NORMAL;
}
-(double) getAndUpdateDesiredStretchFactor {
int prevMode = currentStretchMode;
- (double)getAndUpdateDesiredStretchFactor {
int prevMode = currentStretchMode;
currentStretchMode = [self reconsiderStretchMode];
double factor = STRETCH_MODE_FACTORS[currentStretchMode];
double factor = STRETCH_MODE_FACTORS[currentStretchMode];
if (prevMode != currentStretchMode) {
[stretchModeChangeLogger logValue:factor];
}

View File

@ -7,6 +7,6 @@
*
**/
@protocol AudioCallbackHandler <NSObject>
-(void) handleNewDataRecorded:(CyclicalBuffer*) data;
-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining;
- (void)handleNewDataRecorded:(CyclicalBuffer *)data;
- (void)handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining;
@end

View File

@ -1,5 +1,5 @@
#import <Foundation/Foundation.h>
@protocol BufferDepthMeasure <NSObject>
-(int16_t) currentBufferDepth;
- (int16_t)currentBufferDepth;
@end

View File

@ -2,19 +2,22 @@
enum JitterBadArrivalType {
JitterBadArrivalType_Duplicate = 0, // for when two packets with the same sequence number arrive
JitterBadArrivalType_Stale = 1, // for when sequence number is behind read head
JitterBadArrivalType_TooSoon = 2 // for when sequence number is *way* ahead of read head
JitterBadArrivalType_Stale = 1, // for when sequence number is behind read head
JitterBadArrivalType_TooSoon = 2 // for when sequence number is *way* ahead of read head
};
enum JitterBadDequeueType {
JitterBadDequeueType_Desynced = 0, // for when so many lack-of-datas have accumulated that the read head can skip
JitterBadDequeueType_Empty = 1, // for when there's no data anywhere in the jitter queue
JitterBadDequeueType_NoDataUnderReadHead = 2 // for when there's data in the jitter queue, but it's ahead of the read head
JitterBadDequeueType_Empty = 1, // for when there's no data anywhere in the jitter queue
JitterBadDequeueType_NoDataUnderReadHead =
2 // for when there's data in the jitter queue, but it's ahead of the read head
};
@protocol JitterQueueNotificationReceiver <NSObject>
-(void) notifyArrival:(uint16_t)sequenceNumber;
-(void) notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount;
-(void) notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType;
-(void) notifyBadDequeueOfType:(enum JitterBadDequeueType)type;
-(void) notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber;
-(void) notifyDiscardOverflow:(uint16_t)discardedSequenceNumber resyncingFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber;
- (void)notifyArrival:(uint16_t)sequenceNumber;
- (void)notifyDequeue:(uint16_t)sequenceNumber withRemainingEnqueuedItemCount:(NSUInteger)remainingCount;
- (void)notifyBadArrival:(uint16_t)sequenceNumber ofType:(enum JitterBadArrivalType)arrivalType;
- (void)notifyBadDequeueOfType:(enum JitterBadDequeueType)type;
- (void)notifyResyncFrom:(uint16_t)oldReadHeadSequenceNumber to:(uint16_t)newReadHeadSequenceNumber;
- (void)notifyDiscardOverflow:(uint16_t)discardedSequenceNumber
resyncingFrom:(uint16_t)oldReadHeadSequenceNumber
to:(uint16_t)newReadHeadSequenceNumber;
@end

View File

@ -7,12 +7,15 @@
*
**/
@interface AnonymousAudioCallbackHandler : NSObject<AudioCallbackHandler>
@interface AnonymousAudioCallbackHandler : NSObject <AudioCallbackHandler>
@property (readonly,nonatomic,copy) void (^handleNewDataRecordedBlock)(CyclicalBuffer* data);
@property (readonly,nonatomic,copy) void (^handlePlaybackOccurredWithBytesRequestedBlock)(NSUInteger requested, NSUInteger bytesRemaining);
@property (readonly, nonatomic, copy) void (^handleNewDataRecordedBlock)(CyclicalBuffer *data);
@property (readonly, nonatomic, copy) void (^handlePlaybackOccurredWithBytesRequestedBlock)
(NSUInteger requested, NSUInteger bytesRemaining);
+(AnonymousAudioCallbackHandler*) anonymousAudioInterfaceDelegateWithRecordingCallback:(void(^)(CyclicalBuffer* data))recordingCallback
andPlaybackOccurredCallback:(void(^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback;
+ (AnonymousAudioCallbackHandler *)
anonymousAudioInterfaceDelegateWithRecordingCallback:(void (^)(CyclicalBuffer *data))recordingCallback
andPlaybackOccurredCallback:
(void (^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback;
@end

View File

@ -2,18 +2,20 @@
@implementation AnonymousAudioCallbackHandler
+(AnonymousAudioCallbackHandler*) anonymousAudioInterfaceDelegateWithRecordingCallback:(void(^)(CyclicalBuffer* data))recordingCallback
andPlaybackOccurredCallback:(void(^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback {
AnonymousAudioCallbackHandler* a = [AnonymousAudioCallbackHandler new];
a->_handleNewDataRecordedBlock = recordingCallback;
+ (AnonymousAudioCallbackHandler *)
anonymousAudioInterfaceDelegateWithRecordingCallback:(void (^)(CyclicalBuffer *data))recordingCallback
andPlaybackOccurredCallback:
(void (^)(NSUInteger requested, NSUInteger bytesRemaining))playbackCallback {
AnonymousAudioCallbackHandler *a = [AnonymousAudioCallbackHandler new];
a->_handleNewDataRecordedBlock = recordingCallback;
a->_handlePlaybackOccurredWithBytesRequestedBlock = playbackCallback;
return a;
}
-(void) handleNewDataRecorded:(CyclicalBuffer*)data {
- (void)handleNewDataRecorded:(CyclicalBuffer *)data {
if (_handleNewDataRecordedBlock != nil)
_handleNewDataRecordedBlock(data);
}
-(void) handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining {
- (void)handlePlaybackOccurredWithBytesRequested:(NSUInteger)requested andBytesRemaining:(NSUInteger)bytesRemaining {
if (_handlePlaybackOccurredWithBytesRequestedBlock != nil)
_handlePlaybackOccurredWithBytesRequestedBlock(requested, bytesRemaining);
}

View File

@ -1,8 +1,9 @@
#import <Foundation/Foundation.h>
#import <AddressBook/AddressBook.h>
#import "PhoneNumber.h"
#import "ContactsManager.h"
#import <Foundation/Foundation.h>
#import <TextSecureKit/TSCall.h>
#import "Contact.h"
#import "ContactsManager.h"
#import "PhoneNumber.h"
/**
*
@ -12,11 +13,6 @@
*
*/
typedef enum {
RPRecentCallTypeIncoming = 1,
RPRecentCallTypeOutgoing,
RPRecentCallTypeMissed,
} RPRecentCallType;
extern NSString *const CALL_TYPE_IMAGE_NAME_INCOMING;
extern NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING;
@ -34,5 +30,5 @@ extern NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING;
andNumber:(PhoneNumber *)number
andCallType:(RPRecentCallType)type;
-(void)updateRecentCallWithContactId:(ABRecordID) contactID;
- (void)updateRecentCallWithContactId:(ABRecordID)contactID;
@end

View File

@ -1,10 +1,10 @@
#import "RecentCall.h"
static NSString *const DEFAULTS_KEY_CONTACT_ID = @"DefaultsKeyContactID";
static NSString *const DEFAULTS_KEY_PHONE_NUMBER = @"DefaultsKeyPhoneNumber";
static NSString *const DEFAULTS_KEY_CALL_TYPE = @"DefaultsKeycallType";
static NSString *const DEFAULTS_KEY_DATE = @"DefaultsKeyDate";
static NSString *const DEFAULTS_KEY_IS_ARCHIVED = @"DefaultsKeyDateIsArchived";
static NSString *const DEFAULTS_KEY_CONTACT_ID = @"DefaultsKeyContactID";
static NSString *const DEFAULTS_KEY_PHONE_NUMBER = @"DefaultsKeyPhoneNumber";
static NSString *const DEFAULTS_KEY_CALL_TYPE = @"DefaultsKeycallType";
static NSString *const DEFAULTS_KEY_DATE = @"DefaultsKeyDate";
static NSString *const DEFAULTS_KEY_IS_ARCHIVED = @"DefaultsKeyDateIsArchived";
static NSString *const DEFAULTS_KEY_USER_NOTIFIED = @"DefaultsKeyUserNotified";
NSString *const CALL_TYPE_IMAGE_NAME_INCOMING = @"incoming_call_icon";
@ -17,16 +17,15 @@ NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING = @"outgoing_call_icon";
+ (RecentCall *)recentCallWithContactID:(ABRecordID)contactID
andNumber:(PhoneNumber *)number
andCallType:(RPRecentCallType)type {
RecentCall *recentCall = [RecentCall new];
RecentCall *recentCall = [RecentCall new];
recentCall->contactRecordID = contactID;
recentCall->callType = type;
recentCall->date = [NSDate date];
recentCall->phoneNumber = number;
recentCall->userNotified = type == RPRecentCallTypeMissed ? false : true;
recentCall->callType = type;
recentCall->date = [NSDate date];
recentCall->phoneNumber = number;
recentCall->userNotified = type == RPRecentCallTypeMissed ? false : true;
return recentCall;
}
-(void)updateRecentCallWithContactId:(ABRecordID)contactID{
- (void)updateRecentCallWithContactId:(ABRecordID)contactID {
contactRecordID = contactID;
}
@ -42,13 +41,13 @@ NSString *const CALL_TYPE_IMAGE_NAME_OUTGOING = @"outgoing_call_icon";
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
callType = (RPRecentCallType)[[decoder decodeObjectForKey:DEFAULTS_KEY_CALL_TYPE] intValue];
if ((self = [super init])) {
callType = (RPRecentCallType)[[decoder decodeObjectForKey:DEFAULTS_KEY_CALL_TYPE] intValue];
contactRecordID = [[decoder decodeObjectForKey:DEFAULTS_KEY_CONTACT_ID] intValue];
phoneNumber = [decoder decodeObjectForKey:DEFAULTS_KEY_PHONE_NUMBER];
date = [decoder decodeObjectForKey:DEFAULTS_KEY_DATE];
isArchived = [decoder decodeBoolForKey:DEFAULTS_KEY_IS_ARCHIVED];
userNotified = [decoder decodeBoolForKey:DEFAULTS_KEY_USER_NOTIFIED];
phoneNumber = [decoder decodeObjectForKey:DEFAULTS_KEY_PHONE_NUMBER];
date = [decoder decodeObjectForKey:DEFAULTS_KEY_DATE];
isArchived = [decoder decodeBoolForKey:DEFAULTS_KEY_IS_ARCHIVED];
userNotified = [decoder decodeBoolForKey:DEFAULTS_KEY_USER_NOTIFIED];
}
return self;
}

View File

@ -1,6 +1,6 @@
#import <Foundation/Foundation.h>
#import "RecentCall.h"
#import "PhoneManager.h"
#import "RecentCall.h"
/**
*
@ -12,10 +12,9 @@
@interface RecentCallManager : NSObject
- (void)watchForCallsThrough:(PhoneManager*)phoneManager
untilCancelled:(TOCCancelToken*)untilCancelledToken;
- (void)watchForCallsThrough:(PhoneManager *)phoneManager untilCancelled:(TOCCancelToken *)untilCancelledToken;
- (void)addRecentCall:(RecentCall *)recentCall;
- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor*)incomingCallDescriptor;
- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor *)incomingCallDescriptor;
@end

View File

@ -1,3 +1,5 @@
#import <TextSecureKit/TextSecureKitEnv.h>
#import "NotificationsManager.h"
#import "RecentCallManager.h"
#import "TSCall.h"
#import "TSMessagesManager.h"
@ -9,50 +11,50 @@
@implementation RecentCallManager
- (instancetype)init{
- (instancetype)init {
self = [super init];
if (self) {
_dbConnection = [TSStorageManager sharedManager].newDatabaseConnection;
}
return self;
}
- (void)watchForCallsThrough:(PhoneManager*)phoneManager
untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(phoneManager != nil);
- (void)watchForCallsThrough:(PhoneManager *)phoneManager untilCancelled:(TOCCancelToken *)untilCancelledToken {
ows_require(phoneManager != nil);
[phoneManager.currentCallObservable watchLatestValue:^(CallState* latestCall) {
if (latestCall != nil) {
[self addCall:latestCall];
}
} onThread:NSThread.currentThread untilCancelled:untilCancelledToken];
[phoneManager.currentCallObservable watchLatestValue:^(CallState *latestCall) {
if (latestCall != nil) {
[self addCall:latestCall];
}
}
onThread:NSThread.currentThread
untilCancelled:untilCancelledToken];
}
- (void)addCall:(CallState*)call {
require(call != nil);
[call.futureTermination finallyDo:^(TOCFuture* interactionCompletion) {
bool isOutgoingCall = call.initiatedLocally;
bool isMissedCall = [self isMissedCall:interactionCompletion];
- (void)addCall:(CallState *)call {
ows_require(call != nil);
Contact* contact = [self tryGetContactForCall:call];
RPRecentCallType callType = isOutgoingCall ? RPRecentCallTypeOutgoing
: isMissedCall ? RPRecentCallTypeMissed
: RPRecentCallTypeIncoming;
[self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID
andNumber:call.remoteNumber
andCallType:callType]];
[call.futureTermination finallyDo:^(TOCFuture *interactionCompletion) {
bool isOutgoingCall = call.initiatedLocally;
bool isMissedCall = [self isMissedCall:interactionCompletion];
Contact *contact = [self tryGetContactForCall:call];
RPRecentCallType callType =
isOutgoingCall ? RPRecentCallTypeOutgoing : isMissedCall ? RPRecentCallTypeMissed : RPRecentCallTypeIncoming;
[self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID
andNumber:call.remoteNumber
andCallType:callType]];
}];
}
- (BOOL)isMissedCall:(TOCFuture*)interactionCompletion {
- (BOOL)isMissedCall:(TOCFuture *)interactionCompletion {
if ([interactionCompletion hasResult]) {
if ([[interactionCompletion forceGetResult] isKindOfClass:[CallTermination class]]) {
CallTermination *termination = (CallTermination*)interactionCompletion.forceGetResult;
CallTermination *termination = (CallTermination *)interactionCompletion.forceGetResult;
if (termination.type == CallTerminationType_HangupRemote) {
return YES;
}
@ -61,38 +63,46 @@
return NO;
}
- (Contact*)tryGetContactForCall:(CallState*)call {
if (call.potentiallySpecifiedContact != nil) return call.potentiallySpecifiedContact;
- (Contact *)tryGetContactForCall:(CallState *)call {
if (call.potentiallySpecifiedContact != nil)
return call.potentiallySpecifiedContact;
return [self tryGetContactForNumber:call.remoteNumber];
}
- (Contact*)tryGetContactForNumber:(PhoneNumber*)number {
- (Contact *)tryGetContactForNumber:(PhoneNumber *)number {
return [Environment.getCurrent.contactsManager latestContactForPhoneNumber:number];
}
- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor*)incomingCallDescriptor {
require(incomingCallDescriptor != nil);
Contact* contact = [self tryGetContactForNumber:incomingCallDescriptor.initiatorNumber];
- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor *)incomingCallDescriptor {
ows_require(incomingCallDescriptor != nil);
Contact *contact = [self tryGetContactForNumber:incomingCallDescriptor.initiatorNumber];
[self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID
andNumber:incomingCallDescriptor.initiatorNumber
andCallType:RPRecentCallTypeMissed]];
}
- (void)addRecentCall:(RecentCall*)recentCall {
- (void)addRecentCall:(RecentCall *)recentCall {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:recentCall.phoneNumber.toE164 transaction:transaction];
uint64_t callDateSeconds = (uint64_t)[recentCall.date timeIntervalSince1970];
TSCall *call = [[TSCall alloc] initWithTimestamp:callDateSeconds*1000 withCallNumber:recentCall.phoneNumber.toE164 callType:recentCall.callType inThread:thread];
if(recentCall.isArchived) { //for migration only from Signal versions with RedPhone only
NSDate *date = [NSDate dateWithTimeIntervalSince1970:(callDateSeconds+60)]; // archive has to happen in the future of the original call
[thread archiveThreadWithTransaction:transaction referenceDate:date];
}
[call saveWithTransaction:transaction];
[[TSMessagesManager sharedManager] notifyUserForCall:call inThread:thread];
TSContactThread *thread =
[TSContactThread getOrCreateThreadWithContactId:recentCall.phoneNumber.toE164 transaction:transaction];
uint64_t callDateSeconds = (uint64_t)[recentCall.date timeIntervalSince1970];
TSCall *call = [[TSCall alloc] initWithTimestamp:callDateSeconds * 1000
withCallNumber:recentCall.phoneNumber.toE164
callType:recentCall.callType
inThread:thread];
if (recentCall.isArchived) { // for migration only from Signal versions with RedPhone only
NSDate *date =
[NSDate dateWithTimeIntervalSince1970:(callDateSeconds +
60)]; // archive has to happen in the future of the original call
[thread archiveThreadWithTransaction:transaction referenceDate:date];
}
[call saveWithTransaction:transaction];
NotificationsManager *manager = [TextSecureKitEnv sharedEnv].notificationsManager;
[manager notifyUserForCall:call inThread:thread];
}];
}

View File

@ -1,45 +0,0 @@
#import <Foundation/Foundation.h>
#import <AddressBook/AddressBook.h>
#import "PhoneNumber.h"
/**
*
* Contact represents relevant information related to a contact from the user's contact list.
*
*/
@interface Contact : NSObject
@property (readonly,nonatomic) NSString *firstName;
@property (readonly,nonatomic) NSString *lastName;
@property (readonly,nonatomic) NSArray *parsedPhoneNumbers;
@property (readonly,nonatomic) NSArray *userTextPhoneNumbers;
@property (readonly,nonatomic) NSArray *emails;
@property (readonly,nonatomic) UIImage *image;
@property (readonly,nonatomic) NSString *notes;
@property (readonly,nonatomic) ABRecordID recordID;
+ (Contact*)contactWithFirstName:(NSString*)firstName
andLastName:(NSString *)lastName
andUserTextPhoneNumbers:(NSArray*)phoneNumbers
andEmails:(NSArray*)emails
andContactID:(ABRecordID)record;
+ (Contact*)contactWithFirstName:(NSString*)firstName
andLastName:(NSString *)lastName
andUserTextPhoneNumbers:(NSArray*)numbers
andEmails:(NSArray*)emails
andImage:(UIImage *)image
andContactID:(ABRecordID)record
andNotes:(NSString *)notes;
- (NSString*)fullName;
- (NSString *)allPhoneNumbers;
- (BOOL)isTextSecureContact;
- (BOOL)isRedPhoneContact;
- (NSArray*)textSecureIdentifiers;
- (NSArray*)redPhoneIdentifiers;
@end

View File

@ -1,134 +0,0 @@
#import "Contact.h"
#import "ContactsManager.h"
#import "Environment.h"
#import "SignalRecipient.h"
static NSString *const DEFAULTS_KEY_CONTACT = @"DefaultsKeyContact";
static NSString *const DEFAULTS_KEY_PHONE_NUMBER = @"DefaultsKeyPhoneNumber";
static NSString *const DEFAULTS_KEY_CALL_TYPE = @"DefaultsKeycallType";
static NSString *const DEFAULTS_KEY_DATE = @"DefaultsKeyDate";
@implementation Contact
@synthesize firstName, lastName, emails, image, recordID, notes, parsedPhoneNumbers, userTextPhoneNumbers;
+ (Contact*)contactWithFirstName:(NSString*)firstName
andLastName:(NSString *)lastName
andUserTextPhoneNumbers:(NSArray*)phoneNumbers
andEmails:(NSArray*)emails
andContactID:(ABRecordID)record {
Contact* contact = [Contact new];
contact->firstName = firstName;
contact->lastName = lastName;
contact->userTextPhoneNumbers = phoneNumbers;
contact->emails = emails;
contact->recordID = record;
NSMutableArray *parsedPhoneNumbers = [NSMutableArray array];
for (NSString *phoneNumberString in phoneNumbers) {
PhoneNumber *phoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumberString];
if (phoneNumber) {
[parsedPhoneNumbers addObject:phoneNumber];
}
}
contact->parsedPhoneNumbers = parsedPhoneNumbers.copy;
return contact;
}
+ (Contact*)contactWithFirstName:(NSString*)firstName
andLastName:(NSString *)lastName
andUserTextPhoneNumbers:(NSArray*)numbers
andEmails:(NSArray*)emails
andImage:(UIImage *)image
andContactID:(ABRecordID)record
andNotes:(NSString *)notes {
Contact* contact = [Contact contactWithFirstName:firstName
andLastName:lastName
andUserTextPhoneNumbers:numbers
andEmails:emails
andContactID:record];
contact->image = image;
contact->notes = notes;
return contact;
}
- (NSString *)fullName {
NSMutableString *fullName = [NSMutableString string];
if (firstName) [fullName appendString:firstName];
if (lastName) {
[fullName appendString:[NSString stringWithFormat:@" %@",lastName]];
}
return fullName;
}
- (NSString *)allPhoneNumbers {
NSString * allNumbers = @"";
for (PhoneNumber *number in self.parsedPhoneNumbers) {
allNumbers = [allNumbers stringByAppendingString:number.toE164];
allNumbers = [allNumbers stringByAppendingString:@";"];
}
return allNumbers;
}
-(NSString *)description {
return [NSString stringWithFormat:@"%@ %@: %@", firstName, lastName, userTextPhoneNumbers];
}
- (UIImage *)image {
return image;
}
- (BOOL)isTextSecureContact{
NSArray *identifiers = [self textSecureIdentifiers];
if ([identifiers count] > 0) {
return YES;
}
return NO;
}
- (NSArray*)textSecureIdentifiers{
__block NSMutableArray *identifiers = [NSMutableArray array];
[[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (PhoneNumber *number in self.parsedPhoneNumbers) {
if ([SignalRecipient recipientWithTextSecureIdentifier:number.toE164 withTransaction:transaction]) {
[identifiers addObject:number.toE164];
}
}
}];
return identifiers;
}
- (BOOL)isRedPhoneContact{
NSArray *identifiers = [self redPhoneIdentifiers];
if ([identifiers count] > 0) {
return YES;
}
return NO;
}
- (NSArray *)redPhoneIdentifiers{
__block NSMutableArray *identifiers = [NSMutableArray array];
[[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (PhoneNumber *number in self.parsedPhoneNumbers) {
SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:number.toE164 withTransaction:transaction];
if (recipient && recipient.supportsVoice) {
[identifiers addObject:number.toE164];
}
}
}];
return identifiers;
}
@end

View File

@ -1,27 +0,0 @@
//
// ContactsManager+updater.h
// Signal
//
// Created by Frederic Jacobs on 21/11/15.
// Copyright © 2015 Open Whisper Systems. All rights reserved.
//
#import "SignalRecipient.h"
#import "ContactsManager.h"
@interface ContactsManager (updater)
#define NOTFOUND_ERROR 777404
- (void)intersectContacts;
- (void)synchronousLookup:(NSString*)identifier
success:(void (^)(SignalRecipient*))success
failure:(void (^)(NSError *error))failure;
- (void)lookupIdentifier:(NSString*)identifier
success:(void (^)(NSSet<NSString*> *matchedIds))success
failure:(void (^)(NSError *error))failure;
- (void)updateSignalContactIntersectionWithSuccess:(void (^)())success
failure:(void (^)(NSError *error))failure;
@end

View File

@ -1,184 +0,0 @@
//
// ContactsManager+updater.m
// Signal
//
// Created by Frederic Jacobs on 21/11/15.
// Copyright © 2015 Open Whisper Systems. All rights reserved.
//
#import "ContactsManager+updater.h"
#import "Cryptography.h"
#import "Environment.h"
#import "TSContactsIntersectionRequest.h"
#import "TSNetworkManager.h"
@implementation ContactsManager (updater)
- (void)synchronousLookup:(NSString*)identifier
success:(void (^)(SignalRecipient*))success
failure:(void (^)(NSError *error))failure
{
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block SignalRecipient *recipient = nil;
__block NSError *error = nil;
[self lookupIdentifier:identifier
success:^(NSSet<NSString *> *matchedIds) {
if ([matchedIds count] == 1) {
[[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier withTransaction:transaction];
}];
} else {
error = [NSError errorWithDomain:@"contactsmanager.notfound" code:NOTFOUND_ERROR userInfo:nil];
}
dispatch_semaphore_signal(sema);
} failure:^(NSError *blockerror) {
error = blockerror;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (error) {
SYNC_BLOCK_SAFE_RUN(failure, error);
} else {
SYNC_BLOCK_SAFE_RUN(success, recipient);
}
return;
}
- (void)lookupIdentifier:(NSString*)identifier
success:(void (^)(NSSet<NSString*> *matchedIds))success
failure:(void (^)(NSError *error))failure
{
[self contactIntersectionWithSet:[NSSet setWithObject:identifier]
success:^(NSSet<NSString *> *matchedIds){
BLOCK_SAFE_RUN(success, matchedIds);
}
failure:^(NSError *error) {
BLOCK_SAFE_RUN(failure, error);
}];
}
- (void)intersectContacts {
[self updateSignalContactIntersectionWithSuccess:nil failure:^(NSError *error) {
[NSTimer scheduledTimerWithTimeInterval:60
target:self
selector:@selector(intersectContacts)
userInfo:nil
repeats:NO];
}];
}
- (void)updateSignalContactIntersectionWithSuccess:(void (^)())success
failure:(void (^)(NSError *error))failure
{
NSArray<Contact*> *abContacts = [[[Environment getCurrent] contactsManager] allContacts];
NSMutableSet<NSString*> *abPhoneNumbers = [NSMutableSet set];
for (Contact *contact in abContacts) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
[abPhoneNumbers addObject:phoneNumber.toE164];
}
}
__block NSMutableSet *recipientIds = [NSMutableSet set];
[[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]];
[recipientIds addObjectsFromArray:allRecipientKeys];
}];
NSMutableSet<NSString*> *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy];
[self contactIntersectionWithSet:allContacts
success:^(NSSet<NSString *> *matchedIds) {
[recipientIds minusSet:matchedIds];
// Cleaning up unregistered identifiers
[[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
for (NSString *identifier in recipientIds) {
SignalRecipient *recipient = [SignalRecipient fetchObjectWithUniqueID:identifier transaction:transaction];
[recipient removeWithTransaction:transaction];
}
}];
BLOCK_SAFE_RUN(success);
} failure:^(NSError *error) {
BLOCK_SAFE_RUN(failure, error);
}];
}
- (void) contactIntersectionWithSet:(NSSet<NSString*>*)idSet
success:(void (^)(NSSet<NSString*> *matchedIds))success
failure:(void (^)(NSError *error))failure
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableDictionary *phoneNumbersByHashes = [NSMutableDictionary dictionary];
for (NSString *identifier in idSet) {
[phoneNumbersByHashes setObject:identifier
forKey:[Cryptography truncatedSHA1Base64EncodedWithoutPadding:identifier]];
}
NSArray *hashes = [phoneNumbersByHashes allKeys];
TSRequest *request = [[TSContactsIntersectionRequest alloc] initWithHashesArray:hashes];
[[TSNetworkManager sharedManager] queueAuthenticatedRequest:request success:^(NSURLSessionDataTask *tsTask, id responseDict) {
NSMutableDictionary *attributesForIdentifier = [NSMutableDictionary dictionary];
NSArray *contactsArray = [(NSDictionary*)responseDict objectForKey:@"contacts"];
// Map attributes to phone numbers
if (contactsArray) {
for (NSDictionary *dict in contactsArray) {
NSString *hash = [dict objectForKey:@"token"];
NSString *identifier = [phoneNumbersByHashes objectForKey:hash];
if (!identifier) {
DDLogWarn(@"An interesecting hash wasn't found in the mapping.");
break;
}
[attributesForIdentifier setObject:dict forKey:identifier];
}
}
// Insert or update contact attributes
[[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *identifier in attributesForIdentifier) {
SignalRecipient *recipient = [SignalRecipient recipientWithTextSecureIdentifier:identifier
withTransaction:transaction];
if (!recipient) {
recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier
relay:nil
supportsVoice:NO];
}
NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier];
NSString *relay = [attributes objectForKey:@"relay"];
if (relay) {
recipient.relay = relay;
} else {
recipient.relay = nil;
}
BOOL supportsVoice = [[attributes objectForKey:@"voice"] boolValue];
if (supportsVoice) {
recipient.supportsVoice = YES;
} else {
recipient.supportsVoice = NO;
}
[recipient saveWithTransaction:transaction];
}
}];
BLOCK_SAFE_RUN(success, [NSSet setWithArray:attributesForIdentifier.allKeys]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
BLOCK_SAFE_RUN(failure, error);
}];
});
}
@end

View File

@ -1,8 +1,10 @@
#import <Contacts/Contacts.h>
#import <Foundation/Foundation.h>
#import <TextSecureKit/ContactsManagerProtocol.h>
#import <TextSecureKit/PhoneNumber.h>
#import "CollapsingFutures.h"
#import "Contact.h"
#import "ObservableValue.h"
#import <Contacts/Contacts.h>
/**
*
@ -16,43 +18,43 @@
#define SIGNAL_LIST_UPDATED @"Signal_AB_UPDATED"
typedef void(^ABAccessRequestCompletionBlock)(BOOL hasAccess);
typedef void(^ABReloadRequestCompletionBlock)(NSArray *contacts);
typedef void (^ABAccessRequestCompletionBlock)(BOOL hasAccess);
typedef void (^ABReloadRequestCompletionBlock)(NSArray *contacts);
@interface ContactsManager : NSObject {
@private TOCFuture* futureAddressBook;
@private ObservableValueController* observableContactsController;
@private ObservableValueController* observableTextSecureUsersController;
@private TOCCancelTokenSource* life;
@private NSDictionary *latestContactsById;
@private NSDictionary *latestWhisperUsersById;
@interface ContactsManager : NSObject <ContactsManagerProtocol> {
@private
TOCFuture *futureAddressBook;
@private
ObservableValueController *observableContactsController;
@private
TOCCancelTokenSource *life;
@private
NSDictionary *latestContactsById;
@private
NSDictionary *latestWhisperUsersById;
}
@property CNContactStore *contactStore;
-(ObservableValue *) getObservableContacts;
//-(ObservableValue *) getObservableRedPhoneUsers;
- (ObservableValue *)getObservableContacts;
-(NSArray*) getContactsFromAddressBook:(ABAddressBookRef)addressBook;
-(Contact*) latestContactWithRecordId:(ABRecordID)recordId;
-(Contact*) latestContactForPhoneNumber:(PhoneNumber *)phoneNumber;
-(NSArray*) latestContactsWithSearchString:(NSString *)searchString;
- (NSArray *)getContactsFromAddressBook:(ABAddressBookRef)addressBook;
- (Contact *)latestContactWithRecordId:(ABRecordID)recordId;
- (Contact *)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber;
- (NSArray *)latestContactsWithSearchString:(NSString *)searchString;
+(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString;
+(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString;
+(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString;
+ (NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString;
- (void)verifyABPermission;
- (NSArray<Contact *> *)allContacts;
- (NSArray*)signalContacts;
- (NSArray*)textSecureContacts;
- (NSArray *)signalContacts;
- (NSArray *)textSecureContacts;
-(void)doAfterEnvironmentInitSetup;
- (void)doAfterEnvironmentInitSetup;
- (NSString*)nameStringForPhoneIdentifier:(NSString*)identifier;
- (UIImage*)imageForPhoneIdentifier:(NSString*)identifier;
- (NSString *)nameStringForPhoneIdentifier:(NSString *)identifier;
- (UIImage *)imageForPhoneIdentifier:(NSString *)identifier;
+ (NSComparator)contactComparator;

View File

@ -1,10 +1,11 @@
#import "ContactsManager+updater.h"
#import "ContactsManager.h"
#import "ContactsUpdater.h"
#import "Environment.h"
#import "Util.h"
#define ADDRESSBOOK_QUEUE dispatch_get_main_queue()
typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*);
typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL *);
@interface ContactsManager () {
id addressBookReference;
@ -17,33 +18,35 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*);
- (id)init {
self = [super init];
if (self) {
life = [TOCCancelTokenSource new];
observableContactsController = [ObservableValueController observableValueControllerWithInitialValue:nil];
life = [TOCCancelTokenSource new];
observableContactsController = [ObservableValueController observableValueControllerWithInitialValue:nil];
}
return self;
}
-(void) doAfterEnvironmentInitSetup {
- (void)doAfterEnvironmentInitSetup {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(_iOS_9)) {
self.contactStore = [[CNContactStore alloc] init];
[self.contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!granted) {
// We're still using the old addressbook API.
// User warned if permission not granted in that setup.
}
}];
[self.contactStore requestAccessForEntityType:CNEntityTypeContacts
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!granted) {
// We're still using the old addressbook API.
// User warned if permission not granted in that setup.
}
}];
}
[self setupAddressBook];
[observableContactsController watchLatestValueOnArbitraryThread:^(NSArray *latestContacts) {
@synchronized(self) {
[self setupLatestContacts:latestContacts];
}
} untilCancelled:life.token];
@synchronized(self) {
[self setupLatestContacts:latestContacts];
}
}
untilCancelled:life.token];
}
-(void)dealloc {
- (void)dealloc {
[life cancel];
}
@ -57,37 +60,50 @@ typedef BOOL (^ContactSearchBlock)(id, NSUInteger, BOOL*);
void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef info, void *context);
void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef info, void *context) {
ContactsManager* contactsManager = (__bridge ContactsManager*)context;
ContactsManager *contactsManager = (__bridge ContactsManager *)context;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[contactsManager pullLatestAddressBook];
[contactsManager intersectContacts];
[contactsManager pullLatestAddressBook];
[contactsManager intersectContacts];
});
}
#pragma mark - Setup
-(void) setupAddressBook {
- (void)setupAddressBook {
dispatch_async(ADDRESSBOOK_QUEUE, ^{
[[ContactsManager asyncGetAddressBook] thenDo:^(id addressBook) {
addressBookReference = addressBook;
ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook;
ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void*)self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
[self pullLatestAddressBook];
[self intersectContacts];
});
}];
[[ContactsManager asyncGetAddressBook] thenDo:^(id addressBook) {
addressBookReference = addressBook;
ABAddressBookRef cfAddressBook = (__bridge ABAddressBookRef)addressBook;
ABAddressBookRegisterExternalChangeCallback(cfAddressBook, onAddressBookChanged, (__bridge void *)self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pullLatestAddressBook];
[self intersectContacts];
});
}];
});
}
-(void) pullLatestAddressBook{
CFErrorRef creationError = nil;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError);
checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError) localizedDescription]) ;
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (!granted) {
[ContactsManager blockingContactDialog];
- (void)intersectContacts {
[[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts
success:^{
}
failure:^(NSError *error) {
[NSTimer scheduledTimerWithTimeInterval:60
target:self
selector:@selector(intersectContacts)
userInfo:nil
repeats:NO];
}];
}
- (void)pullLatestAddressBook {
CFErrorRef creationError = nil;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError);
checkOperationDescribe(nil == creationError, [((__bridge NSError *)creationError)localizedDescription]);
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (!granted) {
[ContactsManager blockingContactDialog];
}
});
[observableContactsController updateValue:[self getContactsFromAddressBook:addressBookRef]];
}
@ -98,49 +114,60 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
}
}
+ (void)blockingContactDialog{
+ (void)blockingContactDialog {
switch (ABAddressBookGetAuthorizationStatus()) {
case kABAuthorizationStatusRestricted:{
UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_TITLE", nil)
message:NSLocalizedString(@"ADDRESSBOOK_RESTRICTED_ALERT_BODY", nil)
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ADDRESSBOOK_RESTRICTED_ALERT_BUTTON", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
exit(0);
}]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:controller animated:YES completion:nil];
case kABAuthorizationStatusRestricted: {
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_TITLE", nil)
message:NSLocalizedString(@"ADDRESSBOOK_RESTRICTED_ALERT_BODY", nil)
preferredStyle:UIAlertControllerStyleAlert];
[controller
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ADDRESSBOOK_RESTRICTED_ALERT_BUTTON", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
exit(0);
}]];
[[UIApplication sharedApplication]
.keyWindow.rootViewController presentViewController:controller
animated:YES
completion:nil];
break;
}
case kABAuthorizationStatusDenied: {
UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_TITLE", nil)
message:NSLocalizedString(@"AB_PERMISSION_MISSING_BODY", nil)
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_ACTION", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}]];
[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:controller animated:YES completion:nil];
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_TITLE", nil)
message:NSLocalizedString(@"AB_PERMISSION_MISSING_BODY", nil)
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction
actionWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_ACTION", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[[UIApplication sharedApplication]
openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}]];
[[[UIApplication sharedApplication] keyWindow]
.rootViewController presentViewController:controller
animated:YES
completion:nil];
break;
}
case kABAuthorizationStatusNotDetermined: {
DDLogInfo(@"AddressBook access not granted but status undetermined.");
[[Environment getCurrent].contactsManager pullLatestAddressBook];
break;
}
case kABAuthorizationStatusAuthorized:{
case kABAuthorizationStatusAuthorized: {
DDLogInfo(@"AddressBook access not granted but status authorized.");
break;
}
default:
break;
}
@ -154,76 +181,77 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
#pragma mark - Observables
-(ObservableValue *)getObservableContacts {
- (ObservableValue *)getObservableContacts {
return observableContactsController;
}
#pragma mark - Address Book utils
+(TOCFuture*) asyncGetAddressBook {
CFErrorRef creationError = nil;
+ (TOCFuture *)asyncGetAddressBook {
CFErrorRef creationError = nil;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &creationError);
assert((addressBookRef == nil) == (creationError != nil));
if (creationError != nil) {
[self blockingContactDialog];
return [TOCFuture futureWithFailure:(__bridge_transfer id)creationError];
}
TOCFutureSource *futureAddressBookSource = [TOCFutureSource new];
id addressBook = (__bridge_transfer id)addressBookRef;
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef requestAccessError) {
if (granted && ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
dispatch_async(ADDRESSBOOK_QUEUE,^{
[futureAddressBookSource trySetResult:addressBook];
});
} else {
[self blockingContactDialog];
[futureAddressBookSource trySetFailure:(__bridge id)requestAccessError];
}
if (granted && ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
dispatch_async(ADDRESSBOOK_QUEUE, ^{
[futureAddressBookSource trySetResult:addressBook];
});
} else {
[self blockingContactDialog];
[futureAddressBookSource trySetFailure:(__bridge id)requestAccessError];
}
});
return futureAddressBookSource.future;
}
-(NSArray*) getContactsFromAddressBook:(ABAddressBookRef)addressBook {
- (NSArray *)getContactsFromAddressBook:(ABAddressBookRef)addressBook {
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFMutableArrayRef allPeopleMutable = CFArrayCreateMutableCopy(kCFAllocatorDefault,
CFArrayGetCount(allPeople),allPeople);
CFArraySortValues(allPeopleMutable,CFRangeMake(0, CFArrayGetCount(allPeopleMutable)),
CFMutableArrayRef allPeopleMutable =
CFArrayCreateMutableCopy(kCFAllocatorDefault, CFArrayGetCount(allPeople), allPeople);
CFArraySortValues(allPeopleMutable,
CFRangeMake(0, CFArrayGetCount(allPeopleMutable)),
(CFComparatorFunction)ABPersonComparePeopleByName,
(void*)(unsigned long)ABPersonGetSortOrdering());
(void *)(unsigned long)ABPersonGetSortOrdering());
NSArray *sortedPeople = (__bridge_transfer NSArray *)allPeopleMutable;
// This predicate returns all contacts from the addressbook having at least one phone number
NSPredicate* predicate = [NSPredicate predicateWithBlock: ^BOOL(id record, NSDictionary *bindings) {
ABMultiValueRef phoneNumbers = ABRecordCopyValue( (__bridge ABRecordRef)record, kABPersonPhoneProperty);
BOOL result = NO;
for (CFIndex i = 0; i < ABMultiValueGetCount(phoneNumbers); i++) {
NSString* phoneNumber = (__bridge_transfer NSString*) ABMultiValueCopyValueAtIndex(phoneNumbers, i);
if (phoneNumber.length>0) {
result = YES;
break;
}
}
CFRelease(phoneNumbers);
return result;
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id record, NSDictionary *bindings) {
ABMultiValueRef phoneNumbers = ABRecordCopyValue((__bridge ABRecordRef)record, kABPersonPhoneProperty);
BOOL result = NO;
for (CFIndex i = 0; i < ABMultiValueGetCount(phoneNumbers); i++) {
NSString *phoneNumber = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phoneNumbers, i);
if (phoneNumber.length > 0) {
result = YES;
break;
}
}
CFRelease(phoneNumbers);
return result;
}];
CFRelease(allPeople);
NSArray* filteredContacts = [sortedPeople filteredArrayUsingPredicate:predicate];
NSArray *filteredContacts = [sortedPeople filteredArrayUsingPredicate:predicate];
return [filteredContacts map:^id(id item) {
return [self contactForRecord:(__bridge ABRecordRef)item];
return [self contactForRecord:(__bridge ABRecordRef)item];
}];
}
-(NSArray*)latestContactsWithSearchString:(NSString *)searchString {
- (NSArray *)latestContactsWithSearchString:(NSString *)searchString {
return [latestContactsById.allValues filter:^int(Contact *contact) {
return searchString.length == 0 || [ContactsManager name:contact.fullName matchesQuery:searchString];
return searchString.length == 0 || [ContactsManager name:contact.fullName matchesQuery:searchString];
}];
}
@ -231,50 +259,47 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (Contact *)contactForRecord:(ABRecordRef)record {
ABRecordID recordID = ABRecordGetRecordID(record);
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty);
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty);
NSArray *phoneNumbers = [self phoneNumbersForRecord:record];
if (!firstName && !lastName) {
NSString *companyName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonOrganizationProperty);
NSString *companyName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonOrganizationProperty);
if (companyName) {
firstName = companyName;
} else if (phoneNumbers.count) {
firstName = phoneNumbers.firstObject;
firstName = phoneNumbers.firstObject;
}
}
NSString *notes = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonNoteProperty);
NSArray *emails = [ContactsManager emailsForRecord:record];
NSData *image = (__bridge_transfer NSData*)ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail);
UIImage *img = [UIImage imageWithData:image];
return [Contact contactWithFirstName:firstName
andLastName:lastName
andUserTextPhoneNumbers:phoneNumbers
andEmails:emails
andImage:img
andContactID:recordID
andNotes:notes];
// NSString *notes = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonNoteProperty);
// NSArray *emails = [ContactsManager emailsForRecord:record];
// NSData *image = (__bridge_transfer NSData *)ABPersonCopyImageDataWithFormat(record,
// kABPersonImageFormatThumbnail);
// UIImage *img = [UIImage imageWithData:image];
return [[Contact alloc] initWithContactWithFirstName:firstName
andLastName:lastName
andUserTextPhoneNumbers:phoneNumbers
andContactID:recordID];
}
-(Contact*)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber {
- (Contact *)latestContactForPhoneNumber:(PhoneNumber *)phoneNumber {
NSArray *allContacts = [self allContacts];
ContactSearchBlock searchBlock = ^BOOL(Contact *contact, NSUInteger idx, BOOL *stop) {
for (PhoneNumber *number in contact.parsedPhoneNumbers) {
if ([self phoneNumber:number matchesNumber:phoneNumber]) {
*stop = YES;
return YES;
}
}
return NO;
for (PhoneNumber *number in contact.parsedPhoneNumbers) {
if ([self phoneNumber:number matchesNumber:phoneNumber]) {
*stop = YES;
return YES;
}
}
return NO;
};
NSUInteger contactIndex = [allContacts indexOfObjectPassingTest:searchBlock];
if (contactIndex != NSNotFound) {
return allContacts[contactIndex];
} else {
@ -288,21 +313,22 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (NSArray *)phoneNumbersForRecord:(ABRecordRef)record {
ABMultiValueRef numberRefs = ABRecordCopyValue(record, kABPersonPhoneProperty);
@try {
NSArray *phoneNumbers = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(numberRefs);
if (phoneNumbers == nil) phoneNumbers = @[];
NSArray *phoneNumbers = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(numberRefs);
if (phoneNumbers == nil)
phoneNumbers = @[];
NSMutableArray *numbers = [NSMutableArray array];
for (NSUInteger i = 0; i < phoneNumbers.count; i++) {
NSString *phoneNumber = phoneNumbers[i];
[numbers addObject:phoneNumber];
}
return numbers;
} @finally {
if (numberRefs) {
CFRelease(numberRefs);
@ -310,16 +336,17 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
}
}
+(NSArray *)emailsForRecord:(ABRecordRef)record {
+ (NSArray *)emailsForRecord:(ABRecordRef)record {
ABMultiValueRef emailRefs = ABRecordCopyValue(record, kABPersonEmailProperty);
@try {
NSArray *emails = (__bridge_transfer NSArray*)ABMultiValueCopyArrayOfAllValues(emailRefs);
if (emails == nil) emails = @[];
NSArray *emails = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(emailRefs);
if (emails == nil)
emails = @[];
return emails;
} @finally {
if (emailRefs) {
CFRelease(emailRefs);
@ -327,47 +354,47 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
}
}
+(NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString {
require(contacts != nil);
+ (NSDictionary *)groupContactsByFirstLetter:(NSArray *)contacts matchingSearchString:(NSString *)optionalSearchString {
assert(contacts != nil);
NSArray *matchingContacts = [contacts filter:^int(Contact *contact) {
return optionalSearchString.length == 0 || [self name:contact.fullName matchesQuery:optionalSearchString];
return optionalSearchString.length == 0 || [self name:contact.fullName matchesQuery:optionalSearchString];
}];
return [matchingContacts groupBy:^id(Contact *contact) {
NSString *nameToUse = @"";
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO;
if (firstNameOrdering && contact.firstName != nil && contact.firstName.length > 0) {
nameToUse = contact.firstName;
} else if (!firstNameOrdering && contact.lastName != nil && contact.lastName.length > 0){
nameToUse = contact.lastName;
} else if (contact.lastName == nil) {
if (contact.fullName.length > 0) {
nameToUse = contact.fullName;
} else {
return nameToUse;
}
} else {
nameToUse = contact.lastName;
}
if (nameToUse.length >= 1) {
return [[[nameToUse substringToIndex:1] uppercaseString] decomposedStringWithCompatibilityMapping];
} else{
return @" ";
}
NSString *nameToUse = @"";
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst ? YES : NO;
if (firstNameOrdering && contact.firstName != nil && contact.firstName.length > 0) {
nameToUse = contact.firstName;
} else if (!firstNameOrdering && contact.lastName != nil && contact.lastName.length > 0) {
nameToUse = contact.lastName;
} else if (contact.lastName == nil) {
if (contact.fullName.length > 0) {
nameToUse = contact.fullName;
} else {
return nameToUse;
}
} else {
nameToUse = contact.lastName;
}
if (nameToUse.length >= 1) {
return [[[nameToUse substringToIndex:1] uppercaseString] decomposedStringWithCompatibilityMapping];
} else {
return @" ";
}
}];
}
+(NSDictionary *)keyContactsById:(NSArray *)contacts {
return [contacts keyedBy:^id(Contact* contact) {
return @((int)contact.recordID);
+ (NSDictionary *)keyContactsById:(NSArray *)contacts {
return [contacts keyedBy:^id(Contact *contact) {
return @((int)contact.recordID);
}];
}
-(Contact *)latestContactWithRecordId:(ABRecordID)recordId {
- (Contact *)latestContactWithRecordId:(ABRecordID)recordId {
@synchronized(self) {
return latestContactsById[@(recordId)];
}
@ -375,10 +402,10 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
- (NSArray<Contact *> *)allContacts {
NSMutableArray *allContacts = [NSMutableArray array];
for (NSString *key in latestContactsById.allKeys){
for (NSString *key in latestContactsById.allKeys) {
Contact *contact = [latestContactsById objectForKey:key];
if ([contact isKindOfClass:[Contact class]]) {
[allContacts addObject:contact];
}
@ -386,40 +413,42 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
return allContacts;
}
- (NSArray*)recordsForContacts:(NSArray*) contacts{
- (NSArray *)recordsForContacts:(NSArray *)contacts {
return [contacts map:^id(Contact *contact) {
return @([contact recordID]);
return @([contact recordID]);
}];
}
+(BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString {
+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString {
NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet;
NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet];
NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet];
return [queryStrings all:^int(NSString* query) {
if (query.length == 0) return YES;
return [nameStrings any:^int(NSString* nameWord) {
NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch;
return [nameWord rangeOfString:query options:searchOpts].location != NSNotFound;
}];
NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet];
NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet];
return [queryStrings all:^int(NSString *query) {
if (query.length == 0)
return YES;
return [nameStrings any:^int(NSString *nameWord) {
NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch;
return [nameWord rangeOfString:query options:searchOpts].location != NSNotFound;
}];
}];
}
+(BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString {
+ (BOOL)phoneNumber:(PhoneNumber *)phoneNumber matchesQuery:(NSString *)queryString {
NSString *phoneNumberString = phoneNumber.localizedDescriptionForUser;
NSString *searchString = phoneNumberString.digitsOnly;
if (queryString.length == 0) return YES;
NSString *searchString = phoneNumberString.digitsOnly;
if (queryString.length == 0)
return YES;
NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch;
return [searchString rangeOfString:queryString options:searchOpts].location != NSNotFound;
}
-(NSArray*) contactsForContactIds:(NSArray *)contactIds {
- (NSArray *)contactsForContactIds:(NSArray *)contactIds {
NSMutableArray *contacts = [NSMutableArray array];
for (NSNumber *favouriteId in contactIds) {
Contact *contact = [self latestContactWithRecordId:favouriteId.intValue];
if (contact) {
[contacts addObject:contact];
}
@ -429,47 +458,47 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
#pragma mark - Whisper User Management
-(NSArray*) getSignalUsersFromContactsArray:(NSArray*)contacts {
return [[contacts filter:^int(Contact* contact) {
return contact.isRedPhoneContact || contact.isTextSecureContact;
- (NSArray *)getSignalUsersFromContactsArray:(NSArray *)contacts {
return [[contacts filter:^int(Contact *contact) {
return contact.isRedPhoneContact || contact.isTextSecureContact;
}] sortedArrayUsingComparator:[[self class] contactComparator]];
}
+ (NSComparator)contactComparator {
return ^NSComparisonResult(id obj1, id obj2) {
Contact *contact1 = (Contact*)obj1;
Contact *contact2 = (Contact*)obj2;
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst?YES:NO;
if (firstNameOrdering) {
return [contact1.firstName caseInsensitiveCompare:contact2.firstName];
} else {
return [contact1.lastName caseInsensitiveCompare:contact2.lastName];
};
Contact *contact1 = (Contact *)obj1;
Contact *contact2 = (Contact *)obj2;
BOOL firstNameOrdering = ABPersonGetSortOrdering() == kABPersonCompositeNameFormatFirstNameFirst ? YES : NO;
if (firstNameOrdering) {
return [contact1.firstName caseInsensitiveCompare:contact2.firstName];
} else {
return [contact1.lastName caseInsensitiveCompare:contact2.lastName];
};
};
}
-(NSArray*) signalContacts {
- (NSArray *)signalContacts {
return [self getSignalUsersFromContactsArray:self.allContacts];
}
-(NSArray*) textSecureContacts {
return [[self.allContacts filter:^int(Contact* contact) {
return [contact isTextSecureContact];
- (NSArray *)textSecureContacts {
return [[self.allContacts filter:^int(Contact *contact) {
return [contact isTextSecureContact];
}] sortedArrayUsingComparator:[[self class] contactComparator]];
}
-(NSArray*) getNewItemsFrom:(NSArray*) newArray comparedTo:(NSArray*) oldArray {
- (NSArray *)getNewItemsFrom:(NSArray *)newArray comparedTo:(NSArray *)oldArray {
NSMutableSet *newSet = [NSMutableSet setWithArray:newArray];
NSSet *oldSet = [NSSet setWithArray:oldArray];
NSSet *oldSet = [NSSet setWithArray:oldArray];
[newSet minusSet:oldSet];
return newSet.allObjects;
}
- (NSString*)nameStringForPhoneIdentifier:(NSString*)identifier{
- (NSString *)nameStringForPhoneIdentifier:(NSString *)identifier {
for (Contact *contact in self.allContacts) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
if ([phoneNumber.toE164 isEqualToString:identifier]) {
@ -480,7 +509,7 @@ void onAddressBookChanged(ABAddressBookRef notifyAddressBook, CFDictionaryRef in
return nil;
}
- (UIImage*)imageForPhoneIdentifier:(NSString*)identifier{
- (UIImage *)imageForPhoneIdentifier:(NSString *)identifier {
for (Contact *contact in self.allContacts) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
if ([phoneNumber.toE164 isEqualToString:identifier]) {

View File

@ -6,38 +6,39 @@
@interface CryptoTools : NSObject
/// Returns a secure random 16-bit unsigned integer.
+(uint16_t)generateSecureRandomUInt16;
+ (uint16_t)generateSecureRandomUInt16;
/// Returns a secure random 32-bit unsigned integer.
+(uint32_t)generateSecureRandomUInt32;
+ (uint32_t)generateSecureRandomUInt32;
/// Returns data composed of 'length' cryptographically unpredictable bytes sampled uniformly from [0, 256).
+(NSData*)generateSecureRandomData:(NSUInteger)length;
+ (NSData *)generateSecureRandomData:(NSUInteger)length;
/// Returns the token included as part of HTTP OTP authentication.
+(NSString*) computeOtpWithPassword:(NSString*)password andCounter:(int64_t)counter;
+ (NSString *)computeOtpWithPassword:(NSString *)password andCounter:(int64_t)counter;
@end
@interface NSData (CryptoTools)
-(NSData*)hashWithSha256;
- (NSData *)hashWithSha256;
-(NSData*)hmacWithSha1WithKey:(NSData*)key;
-(NSData*)hmacWithSha256WithKey:(NSData*)key;
- (NSData *)hmacWithSha1WithKey:(NSData *)key;
- (NSData *)hmacWithSha256WithKey:(NSData *)key;
-(NSData*)encryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv;
-(NSData*)decryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv;
- (NSData *)encryptWithAesInCipherFeedbackModeWithKey:(NSData *)key andIv:(NSData *)iv;
- (NSData *)decryptWithAesInCipherFeedbackModeWithKey:(NSData *)key andIv:(NSData *)iv;
-(NSData*)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv;
-(NSData*)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv;
- (NSData *)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData *)key andIv:(NSData *)iv;
- (NSData *)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData *)key andIv:(NSData *)iv;
-(NSData*)encryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv;
-(NSData*)decryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv;
- (NSData *)encryptWithAesInCounterModeWithKey:(NSData *)key andIv:(NSData *)iv;
- (NSData *)decryptWithAesInCounterModeWithKey:(NSData *)key andIv:(NSData *)iv;
/// Determines if two data vectors contain the same information.
/// Avoids short-circuiting or data-dependent branches, so that early returns can't be used to infer where the difference is.
/// Avoids short-circuiting or data-dependent branches, so that early returns can't be used to infer where the
/// difference is.
/// Returns early if data is of different length.
-(bool)isEqualToData_TimingSafe:(NSData*)other;
- (bool)isEqualToData_TimingSafe:(NSData *)other;
@end

View File

@ -8,28 +8,28 @@
@implementation CryptoTools
+(NSData*)generateSecureRandomData:(NSUInteger)length {
NSMutableData* d = [NSMutableData dataWithLength:length];
int err = SecRandomCopyBytes(kSecRandomDefault, length, [d mutableBytes]);
+ (NSData *)generateSecureRandomData:(NSUInteger)length {
NSMutableData *d = [NSMutableData dataWithLength:length];
int err = SecRandomCopyBytes(kSecRandomDefault, length, [d mutableBytes]);
if (err != 0) {
[SecurityFailure raise:@"SecRandomCopyBytes failed"];
}
return d;
}
+(uint16_t)generateSecureRandomUInt16 {
+ (uint16_t)generateSecureRandomUInt16 {
return [[self generateSecureRandomData:sizeof(uint16_t)] bigEndianUInt16At:0];
}
+(uint32_t)generateSecureRandomUInt32 {
+ (uint32_t)generateSecureRandomUInt32 {
return [[self generateSecureRandomData:sizeof(uint32_t)] bigEndianUInt32At:0];
}
+(NSString*) computeOtpWithPassword:(NSString*)password andCounter:(int64_t)counter {
require(password != nil);
NSData* d = [[@(counter) stringValue] encodedAsUtf8];
NSData* h = [d hmacWithSha1WithKey:password.encodedAsUtf8];
+ (NSString *)computeOtpWithPassword:(NSString *)password andCounter:(int64_t)counter {
ows_require(password != nil);
NSData *d = [[@(counter) stringValue] encodedAsUtf8];
NSData *h = [d hmacWithSha1WithKey:password.encodedAsUtf8];
return h.encodedAsBase64;
}
@ -37,41 +37,43 @@
@implementation NSData (CryptoTools)
-(NSData*)hmacWithSha1WithKey:(NSData*)key {
- (NSData *)hmacWithSha1WithKey:(NSData *)key {
return [EvpMessageDigest hmacUsingSha1Data:self withKey:key];
}
-(NSData*)hmacWithSha256WithKey:(NSData*)key {
- (NSData *)hmacWithSha256WithKey:(NSData *)key {
return [EvpMessageDigest hmacUsingSha256Data:self withKey:key];
}
-(NSData*)encryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)encryptWithAesInCipherFeedbackModeWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil encryptMessage:self usingAes128WithCfbAndKey:key andIv:iv];
}
-(NSData*)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)encryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil encryptMessage:self usingAes128WithCbcAndPaddingAndKey:key andIv:iv];
}
-(NSData*)encryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)encryptWithAesInCounterModeWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil encryptMessage:self usingAes128InCounterModeAndKey:key andIv:iv];
}
-(NSData*)decryptWithAesInCipherFeedbackModeWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)decryptWithAesInCipherFeedbackModeWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil decryptMessage:self usingAes128WithCfbAndKey:key andIv:iv];
}
-(NSData*)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)decryptWithAesInCipherBlockChainingModeWithPkcs7PaddingWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil decryptMessage:self usingAes128WithCbcAndPaddingAndKey:key andIv:iv];
}
-(NSData*)decryptWithAesInCounterModeWithKey:(NSData*)key andIv:(NSData*)iv {
- (NSData *)decryptWithAesInCounterModeWithKey:(NSData *)key andIv:(NSData *)iv {
return [EvpSymetricUtil decryptMessage:self usingAes128InCounterModeAndKey:key andIv:iv];
}
-(NSData*)hashWithSha256 {
- (NSData *)hashWithSha256 {
return [EvpMessageDigest hashWithSha256:self];
}
-(bool)isEqualToData_TimingSafe:(NSData*)other {
if (other == nil) return false;
- (bool)isEqualToData_TimingSafe:(NSData *)other {
if (other == nil)
return false;
NSUInteger n = self.length;
if (other.length != n) return false;
if (other.length != n)
return false;
bool equal = true;
for (NSUInteger i = 0; i < n; i++)
equal &= [self uint8At:i] == [other uint8At:i];

View File

@ -4,7 +4,7 @@
@interface EvpMessageDigest : NSObject
+(NSData*) hashWithSha256:(NSData*) data;
+(NSData*) hmacUsingSha1Data:(NSData*) data withKey:(NSData*) key;
+(NSData*) hmacUsingSha256Data:(NSData*) data withKey:(NSData*) key;
+ (NSData *)hashWithSha256:(NSData *)data;
+ (NSData *)hmacUsingSha1Data:(NSData *)data withKey:(NSData *)key;
+ (NSData *)hmacUsingSha256Data:(NSData *)data withKey:(NSData *)key;
@end

View File

@ -9,44 +9,41 @@
@implementation EvpMessageDigest
+(NSData*) hash:(NSData*) data withDigest:(const EVP_MD*) digest {
+ (NSData *)hash:(NSData *)data withDigest:(const EVP_MD *)digest {
NSUInteger expectedDigestLength = [NumberUtil assertConvertIntToNSUInteger:EVP_MD_size(digest)];
unsigned int digestLength = 0;
unsigned int digestLength = 0;
unsigned char digestBuffer[expectedDigestLength];
EVP_MD_CTX* ctx = EVP_MD_CTX_create();
require(NULL != ctx);
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
ows_require(NULL != ctx);
@try {
RAISE_EXCEPTION_ON_FAILURE(EVP_DigestInit_ex(ctx, digest, NULL));
RAISE_EXCEPTION_ON_FAILURE(EVP_DigestUpdate(ctx, data.bytes, data.length));
RAISE_EXCEPTION_ON_FAILURE(EVP_DigestFinal_ex(ctx, digestBuffer, &digestLength));
}
@finally {
} @finally {
EVP_MD_CTX_destroy(ctx);
}
require(digestLength == expectedDigestLength);
ows_require(digestLength == expectedDigestLength);
return [NSData dataWithBytes:digestBuffer length:digestLength];
}
+(NSData*) hmacWithData:(NSData*) data andKey:(NSData*) key andDigest:(const EVP_MD*) md{
+ (NSData *)hmacWithData:(NSData *)data andKey:(NSData *)key andDigest:(const EVP_MD *)md {
NSUInteger digestLength = [NumberUtil assertConvertIntToNSUInteger:EVP_MD_size(md)];
unsigned char* digest = HMAC(md,
[key bytes], [NumberUtil assertConvertNSUIntegerToInt:key.length],
[data bytes], data.length,
NULL, NULL);
return [NSData dataWithBytes:digest length:digestLength];
unsigned char *digest = HMAC(
md, [key bytes], [NumberUtil assertConvertNSUIntegerToInt:key.length], [data bytes], data.length, NULL, NULL);
return [NSData dataWithBytes:digest length:digestLength];
}
+(NSData*) hashWithSha256:(NSData *)data {
+ (NSData *)hashWithSha256:(NSData *)data {
return [self hash:data withDigest:EVP_sha256()];
}
+(NSData*) hmacUsingSha1Data:(NSData*) data withKey:(NSData*) key {
+ (NSData *)hmacUsingSha1Data:(NSData *)data withKey:(NSData *)key {
return [self hmacWithData:data andKey:key andDigest:EVP_sha1()];
}
+(NSData*) hmacUsingSha256Data:(NSData*) data withKey:(NSData*) key{
+ (NSData *)hmacUsingSha256Data:(NSData *)data withKey:(NSData *)key {
return [self hmacWithData:data andKey:key andDigest:EVP_sha256()];
}
@end

View File

@ -5,12 +5,12 @@
@interface EvpSymetricUtil : NSObject
+(NSData*) encryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv;
+(NSData*) decryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv;
+ (NSData *)encryptMessage:(NSData *)message usingAes128WithCbcAndPaddingAndKey:(NSData *)key andIv:(NSData *)iv;
+ (NSData *)decryptMessage:(NSData *)message usingAes128WithCbcAndPaddingAndKey:(NSData *)key andIv:(NSData *)iv;
+(NSData*) encryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv;
+(NSData*) decryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv;
+ (NSData *)encryptMessage:(NSData *)message usingAes128WithCfbAndKey:(NSData *)key andIv:(NSData *)iv;
+ (NSData *)decryptMessage:(NSData *)message usingAes128WithCfbAndKey:(NSData *)key andIv:(NSData *)iv;
+(NSData*) encryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv;
+(NSData*) decryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv;
+ (NSData *)encryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv;
+ (NSData *)decryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv;
@end

View File

@ -6,94 +6,104 @@
@implementation EvpSymetricUtil
+(NSData*) encryptMessage:(NSData *)message usingCipher:(const EVP_CIPHER*) cipher andKey:(NSData *)key andIv:(NSData *)iv {
+ (NSData *)encryptMessage:(NSData *)message
usingCipher:(const EVP_CIPHER *)cipher
andKey:(NSData *)key
andIv:(NSData *)iv {
[self assertKey:key andIv:iv lengthsAgainstCipher:cipher];
int messageLength = [NumberUtil assertConvertNSUIntegerToInt:message.length];
int cipherBlockSize = EVP_CIPHER_block_size(cipher);
int cipherTextLength = 0;
int paddingLength = 0;
int bufferLength = ( messageLength + cipherBlockSize - 1);
int messageLength = [NumberUtil assertConvertNSUIntegerToInt:message.length];
int cipherBlockSize = EVP_CIPHER_block_size(cipher);
int cipherTextLength = 0;
int paddingLength = 0;
int bufferLength = (messageLength + cipherBlockSize - 1);
unsigned char cipherText[bufferLength];
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if(!ctx) { RAISE_EXCEPTION;}
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
RAISE_EXCEPTION;
}
@try {
RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptInit_ex(ctx, cipher, NULL,[key bytes], [iv bytes]))
RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptUpdate(ctx, cipherText, &cipherTextLength, [message bytes], messageLength))
RAISE_EXCEPTION_ON_FAILURE( EVP_EncryptFinal_ex(ctx, cipherText + cipherTextLength, &paddingLength))
RAISE_EXCEPTION_ON_FAILURE(EVP_EncryptInit_ex(ctx, cipher, NULL, [key bytes], [iv bytes]))
RAISE_EXCEPTION_ON_FAILURE(
EVP_EncryptUpdate(ctx, cipherText, &cipherTextLength, [message bytes], messageLength))
RAISE_EXCEPTION_ON_FAILURE(EVP_EncryptFinal_ex(ctx, cipherText + cipherTextLength, &paddingLength))
cipherTextLength += paddingLength;
}
@finally {
} @finally {
EVP_CIPHER_CTX_free(ctx);
}
require(cipherTextLength <= bufferLength);
return [NSData dataWithBytes:cipherText length: [NumberUtil assertConvertIntToNSUInteger:cipherTextLength]];
ows_require(cipherTextLength <= bufferLength);
return [NSData dataWithBytes:cipherText length:[NumberUtil assertConvertIntToNSUInteger:cipherTextLength]];
}
+(void) assertKey:(NSData*) key andIv:(NSData*) iv lengthsAgainstCipher:(const EVP_CIPHER*) cipher {
int cipherKeyLength = EVP_CIPHER_key_length(cipher);
int cipherIvLength = EVP_CIPHER_iv_length(cipher);
require(key.length == [NumberUtil assertConvertIntToNSUInteger:cipherKeyLength]);
require(iv.length == [NumberUtil assertConvertIntToNSUInteger:cipherIvLength]);
+ (void)assertKey:(NSData *)key andIv:(NSData *)iv lengthsAgainstCipher:(const EVP_CIPHER *)cipher {
int cipherKeyLength = EVP_CIPHER_key_length(cipher);
int cipherIvLength = EVP_CIPHER_iv_length(cipher);
ows_require(key.length == [NumberUtil assertConvertIntToNSUInteger:cipherKeyLength]);
ows_require(iv.length == [NumberUtil assertConvertIntToNSUInteger:cipherIvLength]);
}
+(NSData*) decryptMessage:(NSData *)cipherText usingCipher:(const EVP_CIPHER*) cipher andKey:(NSData *)key andIv:(NSData *)iv {
+ (NSData *)decryptMessage:(NSData *)cipherText
usingCipher:(const EVP_CIPHER *)cipher
andKey:(NSData *)key
andIv:(NSData *)iv {
[self assertKey:key andIv:iv lengthsAgainstCipher:cipher];
int cipherTextLength = [NumberUtil assertConvertNSUIntegerToInt:cipherText.length];
int cipherBlockSize = EVP_CIPHER_block_size(cipher);
int plainTextLength = 0;
int paddingLength = 0;
int bufferLength = ( cipherTextLength + cipherBlockSize);
int cipherTextLength = [NumberUtil assertConvertNSUIntegerToInt:cipherText.length];
int cipherBlockSize = EVP_CIPHER_block_size(cipher);
int plainTextLength = 0;
int paddingLength = 0;
int bufferLength = (cipherTextLength + cipherBlockSize);
unsigned char plainText[bufferLength];
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if(!ctx) { RAISE_EXCEPTION;}
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
RAISE_EXCEPTION;
}
@try {
RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptInit_ex( ctx, cipher, NULL, [key bytes], [iv bytes]))
RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptUpdate( ctx, plainText, &plainTextLength, [cipherText bytes], cipherTextLength))
RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptFinal_ex( ctx, plainText + plainTextLength, &paddingLength))
RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptInit_ex(ctx, cipher, NULL, [key bytes], [iv bytes]))
RAISE_EXCEPTION_ON_FAILURE(
EVP_DecryptUpdate(ctx, plainText, &plainTextLength, [cipherText bytes], cipherTextLength))
RAISE_EXCEPTION_ON_FAILURE(EVP_DecryptFinal_ex(ctx, plainText + plainTextLength, &paddingLength))
plainTextLength += paddingLength;
}
@finally {
} @finally {
EVP_CIPHER_CTX_free(ctx);
}
require(plainTextLength <= bufferLength);
return [NSData dataWithBytes:plainText length: [NumberUtil assertConvertIntToNSUInteger:plainTextLength]];
ows_require(plainTextLength <= bufferLength);
return [NSData dataWithBytes:plainText length:[NumberUtil assertConvertIntToNSUInteger:plainTextLength]];
}
+(NSData*) encryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)encryptMessage:(NSData *)message usingAes128WithCbcAndPaddingAndKey:(NSData *)key andIv:(NSData *)iv {
return [self encryptMessage:message usingCipher:EVP_aes_128_cbc() andKey:key andIv:iv];
}
+(NSData*) decryptMessage:(NSData*) message usingAes128WithCbcAndPaddingAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)decryptMessage:(NSData *)message usingAes128WithCbcAndPaddingAndKey:(NSData *)key andIv:(NSData *)iv {
return [self decryptMessage:message usingCipher:EVP_aes_128_cbc() andKey:key andIv:iv];
}
+(NSData*) encryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)encryptMessage:(NSData *)message usingAes128WithCfbAndKey:(NSData *)key andIv:(NSData *)iv {
return [self encryptMessage:message usingCipher:EVP_aes_128_cfb128() andKey:key andIv:iv];
}
+(NSData*) decryptMessage:(NSData*) message usingAes128WithCfbAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)decryptMessage:(NSData *)message usingAes128WithCfbAndKey:(NSData *)key andIv:(NSData *)iv {
return [self decryptMessage:message usingCipher:EVP_aes_128_cfb128() andKey:key andIv:iv];
}
+(NSData*) encryptMessage:(NSData*) message usingAes128InCounterModeAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)encryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv {
return [self encryptMessage:message usingCipher:EVP_aes_128_ctr() andKey:key andIv:iv];
}
+(NSData*) decryptMessage:(NSData*) message usingAes128InCounterModeAndKey:(NSData*) key andIv:(NSData*) iv {
+ (NSData *)decryptMessage:(NSData *)message usingAes128InCounterModeAndKey:(NSData *)key andIv:(NSData *)iv {
return [self decryptMessage:message usingCipher:EVP_aes_128_ctr() andKey:key andIv:iv];
}
@end;
@end
;

View File

@ -1,4 +1,7 @@
#import <Foundation/Foundation.h>
#define RAISE_EXCEPTION [NSException raise:@"OPENSSL_Exception" format:@"Line:%d File:%s ", __LINE__ , __FILE__]
#define RAISE_EXCEPTION_ON_FAILURE(X) if( 1 != X){ RAISE_EXCEPTION;}
#define RAISE_EXCEPTION [NSException raise:@"OPENSSL_Exception" format:@"Line:%d File:%s ", __LINE__, __FILE__]
#define RAISE_EXCEPTION_ON_FAILURE(X) \
if (1 != X) { \
RAISE_EXCEPTION; \
}

View File

@ -12,7 +12,7 @@
@interface DebugLogger : NSObject
MacrosSingletonInterface
+ (instancetype)sharedLogger;
- (void)enableFileLogging;
@ -22,7 +22,7 @@ MacrosSingletonInterface
- (void)wipeLogs;
- (NSString*)logsDirectory;
- (NSString *)logsDirectory;
@property (nonatomic) DDFileLogger *fileLogger;

View File

@ -18,61 +18,73 @@
@implementation DebugLogger
MacrosSingletonImplemention
+ (instancetype)sharedLogger {
static DebugLogger *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [self new];
});
return sharedManager;
}
- (void)enableFileLogging{
self.fileLogger = [[DDFileLogger alloc] init]; //Logging to file, because it's in the Cache folder, they are not uploaded in iTunes/iCloud backups.
self.fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling.
self.fileLogger.logFileManager.maximumNumberOfLogFiles = 3; // Keep three days of logs.
- (void)enableFileLogging {
self.fileLogger = [[DDFileLogger alloc]
init]; // Logging to file, because it's in the Cache folder, they are not uploaded in iTunes/iCloud backups.
self.fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling.
self.fileLogger.logFileManager.maximumNumberOfLogFiles = 3; // Keep three days of logs.
[DDLog addLogger:self.fileLogger];
}
- (void)disableFileLogging{
- (void)disableFileLogging {
[DDLog removeLogger:self.fileLogger];
self.fileLogger = nil;
}
- (void)enableTTYLogging{
- (void)enableTTYLogging {
[DDLog addLogger:DDTTYLogger.sharedInstance];
}
- (void)wipeLogs{
BOOL reenableLogging = (self.fileLogger?YES:NO);
- (void)wipeLogs {
BOOL reenableLogging = (self.fileLogger ? YES : NO);
NSError *error;
NSArray *logsPath = self.fileLogger.logFileManager.unsortedLogFilePaths;
if (reenableLogging) {
[self disableFileLogging];
}
for (NSUInteger i = 0; i < logsPath.count; i++) {
for (NSUInteger i = 0; i < logsPath.count; i++) {
[[NSFileManager defaultManager] removeItemAtPath:[logsPath objectAtIndex:i] error:&error];
}
if (error) {
DDLogError(@"Logs couldn't be removed. %@", error.description);
}
if (reenableLogging) {
[self enableFileLogging];
}
}
- (NSString*)logsDirectory{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
- (NSString *)logsDirectory {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
if (![[NSFileManager defaultManager] fileExistsAtPath:logsDirectory]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:logsDirectory withIntermediateDirectories:YES attributes:nil error:nil];
[[NSFileManager defaultManager] createDirectoryAtPath:logsDirectory
withIntermediateDirectories:YES
attributes:nil
error:nil];
if (error) {
DDLogError(@"Log folder couldn't be created. %@", error.description);
}
}
return logsDirectory;
}

View File

@ -1,12 +1,12 @@
#import <Foundation/Foundation.h>
#import "Logging.h"
#import "PropertyListPreferences.h"
#import "PacketHandler.h"
#import "PropertyListPreferences.h"
#import "SecureEndPoint.h"
#import "TSGroupModel.h"
#import "TSStorageHeaders.h"
static NSString* const kCallSegue = @"2.0_6.0_Call_Segue";
static NSString *const kCallSegue = @"2.0_6.0_Call_Segue";
/**
*
@ -20,7 +20,8 @@ static NSString* const kCallSegue = @"2.0_6.0_Call_Segue";
#define ENVIRONMENT_TESTING_OPTION_LOSE_CONF_ACK_ON_PURPOSE @"LoseConfAck"
#define ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS @"AllowTcpWithoutTls"
#define ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER @"LegacyAndroidInterop_1"
#define ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER \
@"LegacyAndroidInterop_1"
#define TESTING_OPTION_USE_DH_FOR_HANDSHAKE @"DhKeyAgreementOnly"
@class RecentCallManager;
@ -31,62 +32,59 @@ static NSString* const kCallSegue = @"2.0_6.0_Call_Segue";
@interface Environment : NSObject
@property (nonatomic, readonly) in_port_t serverPort;
@property (nonatomic, readonly) id<Logging> logging;
@property (nonatomic, readonly) SecureEndPoint* masterServerSecureEndPoint;
@property (nonatomic, readonly) NSString* defaultRelayName;
@property (nonatomic, readonly) Certificate* certificate;
@property (nonatomic, readonly) NSString* relayServerHostNameSuffix;
@property (nonatomic, readonly) NSArray* keyAgreementProtocolsInDescendingPriority;
@property (nonatomic, readonly) SecureEndPoint *masterServerSecureEndPoint;
@property (nonatomic, readonly) NSString *defaultRelayName;
@property (nonatomic, readonly) Certificate *certificate;
@property (nonatomic, readonly) NSString *relayServerHostNameSuffix;
@property (nonatomic, readonly) NSArray *keyAgreementProtocolsInDescendingPriority;
@property (nonatomic, readonly) ErrorHandlerBlock errorNoter;
@property (nonatomic, readonly) NSString* currentRegionCodeForPhoneNumbers;
@property (nonatomic, readonly) PhoneManager* phoneManager;
@property (nonatomic, readonly) PhoneManager *phoneManager;
@property (nonatomic, readonly) RecentCallManager *recentCallManager;
@property (nonatomic, readonly) NSArray* testingAndLegacyOptions;
@property (nonatomic, readonly) NSData* zrtpClientId;
@property (nonatomic, readonly) NSData* zrtpVersionId;
@property (nonatomic, readonly) NSArray *testingAndLegacyOptions;
@property (nonatomic, readonly) NSData *zrtpClientId;
@property (nonatomic, readonly) NSData *zrtpVersionId;
@property (nonatomic, readonly) ContactsManager *contactsManager;
@property (nonatomic, readonly) SignalsViewController *signalsViewController;
@property (nonatomic, readonly, weak) UINavigationController *signUpFlowNavigationController;
+(SecureEndPoint*) getMasterServerSecureEndPoint;
+(SecureEndPoint*) getSecureEndPointToDefaultRelayServer;
+(SecureEndPoint*) getSecureEndPointToSignalingServerNamed:(NSString*)name;
+ (SecureEndPoint *)getMasterServerSecureEndPoint;
+ (SecureEndPoint *)getSecureEndPointToDefaultRelayServer;
+ (SecureEndPoint *)getSecureEndPointToSignalingServerNamed:(NSString *)name;
+(Environment*) environmentWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort
andMasterServerHostName:(NSString*)masterServerHostName
andDefaultRelayName:(NSString*)defaultRelayName
andRelayServerHostNameSuffix:(NSString*)relayServerHostNameSuffix
andCertificate:(Certificate*)certificate
andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers
andSupportedKeyAgreementProtocols:(NSArray*)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager*)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray*)testingAndLegacyOptions
andZrtpClientId:(NSData*)zrtpClientId
andZrtpVersionId:(NSData*)zrtpVersionId
andContactsManager:(ContactsManager *)contactsManager;
+ (Environment *)environmentWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort
andMasterServerHostName:(NSString *)masterServerHostName
andDefaultRelayName:(NSString *)defaultRelayName
andRelayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix
andCertificate:(Certificate *)certificate
andSupportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager *)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray *)testingAndLegacyOptions
andZrtpClientId:(NSData *)zrtpClientId
andZrtpVersionId:(NSData *)zrtpVersionId
andContactsManager:(ContactsManager *)contactsManager;
+(Environment*) getCurrent;
+(void) setCurrent:(Environment*)curEnvironment;
+(id<Logging>) logging;
+(NSString*) relayServerNameToHostName:(NSString*)name;
+(ErrorHandlerBlock) errorNoter;
+(NSString*) currentRegionCodeForPhoneNumbers;
+(bool) hasEnabledTestingOrLegacyOption:(NSString*)flag;
+(PhoneManager*) phoneManager;
+ (Environment *)getCurrent;
+ (void)setCurrent:(Environment *)curEnvironment;
+ (id<Logging>)logging;
+ (NSString *)relayServerNameToHostName:(NSString *)name;
+ (ErrorHandlerBlock)errorNoter;
+ (bool)hasEnabledTestingOrLegacyOption:(NSString *)flag;
+ (PhoneManager *)phoneManager;
+(PropertyListPreferences*)preferences;
+ (PropertyListPreferences *)preferences;
+(BOOL)isRedPhoneRegistered;
+(void)resetAppData;
+ (BOOL)isRedPhoneRegistered;
+ (void)resetAppData;
- (void)initCallListener;
- (void)setSignalsViewController:(SignalsViewController *)signalsViewController;
- (void)setSignUpFlowNavigationController:(UINavigationController *)signUpFlowNavigationController;
+ (void)messageThreadId:(NSString*)threadId;
+ (void)messageIdentifier:(NSString*)identifier withCompose:(BOOL)compose;
+ (void)messageGroupModel:(TSGroupModel*)model withCompose:(BOOL)compose;
+ (void)messageThreadId:(NSString *)threadId;
+ (void)messageIdentifier:(NSString *)identifier withCompose:(BOOL)compose;
+ (void)messageGroupModel:(TSGroupModel *)model withCompose:(BOOL)compose;
@end

View File

@ -1,11 +1,11 @@
#import "Constraints.h"
#import "DH3KKeyAgreementProtocol.h"
#import "DebugLogger.h"
#import "Environment.h"
#import "Constraints.h"
#import "FunctionalUtil.h"
#import "KeyAgreementProtocol.h"
#import "DH3KKeyAgreementProtocol.h"
#import "RecentCallManager.h"
#import "MessagesViewController.h"
#import "RecentCallManager.h"
#import "SignalKeyingStorage.h"
#import "SignalsViewController.h"
#import "TSContactThread.h"
@ -13,164 +13,145 @@
#define isRegisteredUserDefaultString @"isRegistered"
static Environment* environment = nil;
static Environment *environment = nil;
@implementation Environment
@synthesize testingAndLegacyOptions,
currentRegionCodeForPhoneNumbers,
errorNoter,
keyAgreementProtocolsInDescendingPriority,
logging,
masterServerSecureEndPoint,
defaultRelayName,
relayServerHostNameSuffix,
certificate,
serverPort,
zrtpClientId,
zrtpVersionId,
phoneManager,
recentCallManager,
contactsManager;
@synthesize testingAndLegacyOptions, errorNoter, keyAgreementProtocolsInDescendingPriority, logging,
masterServerSecureEndPoint, defaultRelayName, relayServerHostNameSuffix, certificate, serverPort, zrtpClientId,
zrtpVersionId, phoneManager, recentCallManager, contactsManager;
+(NSString*) currentRegionCodeForPhoneNumbers {
return self.getCurrent.currentRegionCodeForPhoneNumbers;
}
+(Environment*) getCurrent {
require(environment != nil);
+ (Environment *)getCurrent {
NSAssert((environment != nil), @"Environment is not defined.");
return environment;
}
+(void) setCurrent:(Environment*)curEnvironment {
+ (void)setCurrent:(Environment *)curEnvironment {
environment = curEnvironment;
}
+(ErrorHandlerBlock) errorNoter {
+ (ErrorHandlerBlock)errorNoter {
return self.getCurrent.errorNoter;
}
+(bool) hasEnabledTestingOrLegacyOption:(NSString*)flag {
+ (bool)hasEnabledTestingOrLegacyOption:(NSString *)flag {
return [self.getCurrent.testingAndLegacyOptions containsObject:flag];
}
+(NSString*) relayServerNameToHostName:(NSString*)name {
return [NSString stringWithFormat:@"%@.%@",
name,
Environment.getCurrent.relayServerHostNameSuffix];
+ (NSString *)relayServerNameToHostName:(NSString *)name {
return [NSString stringWithFormat:@"%@.%@", name, Environment.getCurrent.relayServerHostNameSuffix];
}
+(SecureEndPoint*) getMasterServerSecureEndPoint {
+ (SecureEndPoint *)getMasterServerSecureEndPoint {
return Environment.getCurrent.masterServerSecureEndPoint;
}
+(SecureEndPoint*) getSecureEndPointToDefaultRelayServer {
+ (SecureEndPoint *)getSecureEndPointToDefaultRelayServer {
return [Environment getSecureEndPointToSignalingServerNamed:Environment.getCurrent.defaultRelayName];
}
+(SecureEndPoint*) getSecureEndPointToSignalingServerNamed:(NSString*)name {
require(name != nil);
Environment* env = Environment.getCurrent;
NSString* hostName = [self relayServerNameToHostName:name];
HostNameEndPoint* location = [HostNameEndPoint hostNameEndPointWithHostName:hostName andPort:env.serverPort];
+ (SecureEndPoint *)getSecureEndPointToSignalingServerNamed:(NSString *)name {
ows_require(name != nil);
Environment *env = Environment.getCurrent;
NSString *hostName = [self relayServerNameToHostName:name];
HostNameEndPoint *location = [HostNameEndPoint hostNameEndPointWithHostName:hostName andPort:env.serverPort];
return [SecureEndPoint secureEndPointForHost:location identifiedByCertificate:env.certificate];
}
+(Environment*) environmentWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort
andMasterServerHostName:(NSString*)masterServerHostName
andDefaultRelayName:(NSString*)defaultRelayName
andRelayServerHostNameSuffix:(NSString*)relayServerHostNameSuffix
andCertificate:(Certificate*)certificate
andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers
andSupportedKeyAgreementProtocols:(NSArray*)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager*)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray*)testingAndLegacyOptions
andZrtpClientId:(NSData*)zrtpClientId
andZrtpVersionId:(NSData*)zrtpVersionId
andContactsManager:(ContactsManager *)contactsManager {
require(errorNoter != nil);
require(zrtpClientId != nil);
require(zrtpVersionId != nil);
require(testingAndLegacyOptions != nil);
require(currentRegionCodeForPhoneNumbers != nil);
require(keyAgreementProtocolsInDescendingPriority != nil);
require([keyAgreementProtocolsInDescendingPriority all:^int(id p) {
return [p conformsToProtocol:@protocol(KeyAgreementProtocol)];
+ (Environment *)environmentWithLogging:(id<Logging>)logging
andErrorNoter:(ErrorHandlerBlock)errorNoter
andServerPort:(in_port_t)serverPort
andMasterServerHostName:(NSString *)masterServerHostName
andDefaultRelayName:(NSString *)defaultRelayName
andRelayServerHostNameSuffix:(NSString *)relayServerHostNameSuffix
andCertificate:(Certificate *)certificate
andSupportedKeyAgreementProtocols:(NSArray *)keyAgreementProtocolsInDescendingPriority
andPhoneManager:(PhoneManager *)phoneManager
andRecentCallManager:(RecentCallManager *)recentCallManager
andTestingAndLegacyOptions:(NSArray *)testingAndLegacyOptions
andZrtpClientId:(NSData *)zrtpClientId
andZrtpVersionId:(NSData *)zrtpVersionId
andContactsManager:(ContactsManager *)contactsManager {
ows_require(errorNoter != nil);
ows_require(zrtpClientId != nil);
ows_require(zrtpVersionId != nil);
ows_require(testingAndLegacyOptions != nil);
ows_require(keyAgreementProtocolsInDescendingPriority != nil);
ows_require([keyAgreementProtocolsInDescendingPriority all:^int(id p) {
return [p conformsToProtocol:@protocol(KeyAgreementProtocol)];
}]);
// must support DH3k
require([keyAgreementProtocolsInDescendingPriority any:^int(id p) {
return [p isKindOfClass:DH3KKeyAgreementProtocol.class];
ows_require([keyAgreementProtocolsInDescendingPriority any:^int(id p) {
return [p isKindOfClass:DH3KKeyAgreementProtocol.class];
}]);
Environment* e = [Environment new];
e->errorNoter = errorNoter;
e->logging = logging;
e->testingAndLegacyOptions = testingAndLegacyOptions;
e->serverPort = serverPort;
e->masterServerSecureEndPoint = [SecureEndPoint secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName
andPort:serverPort]
identifiedByCertificate:certificate];
e->defaultRelayName = defaultRelayName;
e->certificate = certificate;
e->relayServerHostNameSuffix = relayServerHostNameSuffix;
Environment *e = [Environment new];
e->errorNoter = errorNoter;
e->logging = logging;
e->testingAndLegacyOptions = testingAndLegacyOptions;
e->serverPort = serverPort;
e->masterServerSecureEndPoint = [SecureEndPoint
secureEndPointForHost:[HostNameEndPoint hostNameEndPointWithHostName:masterServerHostName andPort:serverPort]
identifiedByCertificate:certificate];
e->defaultRelayName = defaultRelayName;
e->certificate = certificate;
e->relayServerHostNameSuffix = relayServerHostNameSuffix;
e->keyAgreementProtocolsInDescendingPriority = keyAgreementProtocolsInDescendingPriority;
e->currentRegionCodeForPhoneNumbers = currentRegionCodeForPhoneNumbers;
e->phoneManager = phoneManager;
e->recentCallManager = recentCallManager;
e->zrtpClientId = zrtpClientId;
e->zrtpVersionId = zrtpVersionId;
e->contactsManager = contactsManager;
e->phoneManager = phoneManager;
e->recentCallManager = recentCallManager;
e->zrtpClientId = zrtpClientId;
e->zrtpVersionId = zrtpVersionId;
e->contactsManager = contactsManager;
if (recentCallManager != nil) {
// recentCallManagers are nil in unit tests because they would require unnecessary allocations. Detailed explanation: https://github.com/WhisperSystems/Signal-iOS/issues/62#issuecomment-51482195
[recentCallManager watchForCallsThrough:phoneManager
untilCancelled:nil];
// recentCallManagers are nil in unit tests because they would require unnecessary allocations. Detailed
// explanation: https://github.com/WhisperSystems/Signal-iOS/issues/62#issuecomment-51482195
[recentCallManager watchForCallsThrough:phoneManager untilCancelled:nil];
}
return e;
}
+(PhoneManager*) phoneManager {
+ (PhoneManager *)phoneManager {
return Environment.getCurrent.phoneManager;
}
+(id<Logging>) logging {
+ (id<Logging>)logging {
// Many tests create objects that rely on Environment only for logging.
// So we bypass the nil check in getCurrent and silently don't log during unit testing, instead of failing hard.
if (environment == nil) return nil;
if (environment == nil)
return nil;
return Environment.getCurrent.logging;
}
+(BOOL)isRedPhoneRegistered{
+ (BOOL)isRedPhoneRegistered {
// Attributes that need to be set
NSData *signalingKey = SignalKeyingStorage.signalingCipherKey;
NSData *macKey = SignalKeyingStorage.signalingMacKey;
NSData *extra = SignalKeyingStorage.signalingExtraKey;
NSString *serverAuth = SignalKeyingStorage.serverAuthPassword;
return signalingKey && macKey && extra && serverAuth;
}
- (void)initCallListener {
[self.phoneManager.currentCallObservable watchLatestValue:^(CallState* latestCall) {
if (latestCall == nil){
return;
}
SignalsViewController *vc = [[Environment getCurrent] signalsViewController];
[vc dismissViewControllerAnimated:NO completion:nil];
vc.latestCall = latestCall;
[vc performSegueWithIdentifier:kCallSegue sender:self];
} onThread:NSThread.mainThread untilCancelled:nil];
[self.phoneManager.currentCallObservable watchLatestValue:^(CallState *latestCall) {
if (latestCall == nil) {
return;
}
SignalsViewController *vc = [[Environment getCurrent] signalsViewController];
[vc dismissViewControllerAnimated:NO completion:nil];
vc.latestCall = latestCall;
[vc performSegueWithIdentifier:kCallSegue sender:self];
}
onThread:NSThread.mainThread
untilCancelled:nil];
}
+(PropertyListPreferences*)preferences{
+ (PropertyListPreferences *)preferences {
return [PropertyListPreferences new];
}
- (void)setSignalsViewController:(SignalsViewController *)signalsViewController{
- (void)setSignalsViewController:(SignalsViewController *)signalsViewController {
_signalsViewController = signalsViewController;
}
@ -178,76 +159,77 @@ contactsManager;
_signUpFlowNavigationController = navigationController;
}
+ (void)messageThreadId:(NSString*)threadId {
+ (void)messageThreadId:(NSString *)threadId {
TSThread *thread = [TSThread fetchObjectWithUniqueID:threadId];
if (!thread) {
DDLogWarn(@"We get UILocalNotifications with unknown threadId: %@", threadId);
return;
}
if ([thread isGroupThread]) {
[self messageGroup:(TSGroupThread*)thread];
[self messageGroup:(TSGroupThread *)thread];
} else {
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
UIViewController *topvc = vc.navigationController.topViewController;
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
UIViewController *topvc = vc.navigationController.topViewController;
if ([topvc isKindOfClass:[MessagesViewController class]]) {
MessagesViewController *mvc = (MessagesViewController*)topvc;
MessagesViewController *mvc = (MessagesViewController *)topvc;
if ([mvc.thread.uniqueId isEqualToString:threadId]) {
[mvc popKeyBoard];
return;
}
}
[self messageIdentifier:((TSContactThread*)thread).contactIdentifier withCompose:YES];
[self messageIdentifier:((TSContactThread *)thread).contactIdentifier withCompose:YES];
}
}
+ (void)messageIdentifier:(NSString*)identifier withCompose:(BOOL)compose {
+ (void)messageIdentifier:(NSString *)identifier withCompose:(BOOL)compose {
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
if (vc.presentedViewController) {
[vc.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
[vc.navigationController popToRootViewControllerAnimated:NO];
vc.contactIdentifierFromCompose = identifier;
vc.composeMessage = compose;
[vc performSegueWithIdentifier:@"showSegue" sender:nil];
}
+ (void)messageGroup:(TSGroupThread*)groupThread {
+ (void)messageGroup:(TSGroupThread *)groupThread {
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
if (vc.presentedViewController) {
[vc.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
[vc.navigationController popToRootViewControllerAnimated:NO];
[vc performSegueWithIdentifier:@"showSegue" sender:groupThread];
}
+ (void)messageGroupModel:(TSGroupModel*)model withCompose:(BOOL)compose {
+ (void)messageGroupModel:(TSGroupModel *)model withCompose:(BOOL)compose {
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
if (vc.presentedViewController) {
[vc.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
[vc.navigationController popToRootViewControllerAnimated:NO];
vc.groupFromCompose = model;
vc.composeMessage = compose;
[vc performSegueWithIdentifier:@"showSegue" sender:nil];
}
+ (void)resetAppData{
+ (void)resetAppData {
[[TSStorageManager sharedManager] wipeSignalStorage];
[Environment.preferences clear];
[DebugLogger.sharedInstance wipeLogs];
[DebugLogger.sharedLogger wipeLogs];
exit(0);
}
@end

View File

@ -1,43 +1,38 @@
#import <Foundation/Foundation.h>
#import "CallTermination.h"
#import "CallProgress.h"
#import "CallTermination.h"
#define TXT_IN_CALL_CONNECTING NSLocalizedString(@"IN_CALL_CONNECTING", @"")
#define TXT_IN_CALL_RINGING NSLocalizedString(@"IN_CALL_RINGING", @"")
#define TXT_IN_CALL_SECURING NSLocalizedString(@"IN_CALL_SECURING", @"")
#define TXT_IN_CALL_TALKING NSLocalizedString(@"IN_CALL_TALKING", @"")
#define TXT_IN_CALL_TERMINATED NSLocalizedString(@"IN_CALL_TERMINATED", @"")
#define TXT_IN_CALL_CONNECTING NSLocalizedString(@"IN_CALL_CONNECTING", @"")
#define TXT_IN_CALL_RINGING NSLocalizedString(@"IN_CALL_RINGING", @"")
#define TXT_IN_CALL_SECURING NSLocalizedString(@"IN_CALL_SECURING", @"")
#define TXT_IN_CALL_TALKING NSLocalizedString(@"IN_CALL_TALKING", @"")
#define TXT_IN_CALL_TERMINATED NSLocalizedString(@"IN_CALL_TERMINATED", @"")
#define TXT_END_CALL_LOGIN_FAILED NSLocalizedString(@"END_CALL_LOGIN_FAILED", @"")
#define TXT_END_CALL_STALE_SESSION NSLocalizedString(@"END_CALL_STALE_SESSION", @"")
#define TXT_END_CALL_NO_SUCH_USER NSLocalizedString(@"END_CALL_NO_SUCH_USER", @"")
#define TXT_END_CALL_RESPONDER_IS_BUSY NSLocalizedString(@"END_CALL_RESPONDER_IS_BUSY", @"")
#define TXT_END_CALL_REJECTED_LOCAL NSLocalizedString(@"END_CALL_REJECTED_LOCAL", @"")
#define TXT_END_CALL_REJECTED_REMOTE NSLocalizedString(@"END_CALL_REJECTED_REMOTE", @"")
#define TXT_END_CALL_RECIPIENT_UNAVAILABLE NSLocalizedString(@"END_CALL_RECIPIENT_UNAVAILABLE", @"")
#define TXT_END_CALL_UNCATEGORIZED_FAILURE NSLocalizedString(@"END_CALL_UNCATEGORIZED_FAILURE", @"")
#define TXT_END_CALL_BAD_INTERACTION_WITH_SERVER NSLocalizedString(@"END_CALL_BAD_INTERACTION_WITH_SERVER", @"")
#define TXT_END_CALL_HANDSHAKE_FAILED NSLocalizedString(@"END_CALL_HANDSHAKE_FAILED", @"")
#define TXT_END_CALL_HANGUP_REMOTE NSLocalizedString(@"END_CALL_HANGUP_REMOTE", @"")
#define TXT_END_CALL_HANGUP_LOCAL NSLocalizedString(@"END_CALL_HANGUP_LOCAL", @"")
#define TXT_END_CALL_REPLACED_BY_NEXT NSLocalizedString(@"END_CALL_REPLACED_BY_NEXT", @"")
#define TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX NSLocalizedString(@"END_CALL_MESSAGE_FROM_SERVER_PREFIX", @"")
#define TXT_END_CALL_LOGIN_FAILED NSLocalizedString(@"END_CALL_LOGIN_FAILED", @"")
#define TXT_END_CALL_STALE_SESSION NSLocalizedString(@"END_CALL_STALE_SESSION", @"")
#define TXT_END_CALL_NO_SUCH_USER NSLocalizedString(@"END_CALL_NO_SUCH_USER", @"")
#define TXT_END_CALL_RESPONDER_IS_BUSY NSLocalizedString(@"END_CALL_RESPONDER_IS_BUSY", @"")
#define TXT_END_CALL_REJECTED_LOCAL NSLocalizedString(@"END_CALL_REJECTED_LOCAL", @"")
#define TXT_END_CALL_REJECTED_REMOTE NSLocalizedString(@"END_CALL_REJECTED_REMOTE", @"")
#define TXT_END_CALL_RECIPIENT_UNAVAILABLE NSLocalizedString(@"END_CALL_RECIPIENT_UNAVAILABLE", @"")
#define TXT_END_CALL_UNCATEGORIZED_FAILURE NSLocalizedString(@"END_CALL_UNCATEGORIZED_FAILURE", @"")
#define TXT_END_CALL_BAD_INTERACTION_WITH_SERVER NSLocalizedString(@"END_CALL_BAD_INTERACTION_WITH_SERVER", @"")
#define TXT_END_CALL_HANDSHAKE_FAILED NSLocalizedString(@"END_CALL_HANDSHAKE_FAILED", @"")
#define TXT_END_CALL_HANGUP_REMOTE NSLocalizedString(@"END_CALL_HANGUP_REMOTE", @"")
#define TXT_END_CALL_HANGUP_LOCAL NSLocalizedString(@"END_CALL_HANGUP_LOCAL", @"")
#define TXT_END_CALL_REPLACED_BY_NEXT NSLocalizedString(@"END_CALL_REPLACED_BY_NEXT", @"")
#define TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX NSLocalizedString(@"END_CALL_MESSAGE_FROM_SERVER_PREFIX", @"")
#pragma mark - View Controller Titles
#define WHISPER_NAV_BAR_TITLE NSLocalizedString(@"WHISPER_NAV_BAR_TITLE", @"Title for home feed view controller")
#define CONTACT_BROWSE_NAV_BAR_TITLE NSLocalizedString(@"CONTACT_BROWSE_NAV_BAR_TITLE", @"Title for contact browse view controller")
#define KEYPAD_NAV_BAR_TITLE NSLocalizedString(@"KEYPAD_NAV_BAR_TITLE", @"Title for keypad view controller")
#define RECENT_NAV_BAR_TITLE NSLocalizedString(@"RECENT_NAV_BAR_TITLE", @"Title for recent calls view controller")
#define SETTINGS_NAV_BAR_TITLE NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for recent calls view controller")
#define FAVOURITES_NAV_BAR_TITLE NSLocalizedString(@"FAVOURITES_NAV_BAR_TITLE", @"Title for favourites view controller")
#define SETTINGS_NAV_BAR_TITLE NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for recent calls view controller")
#define FAVOURITES_NAV_BAR_TITLE NSLocalizedString(@"FAVOURITES_NAV_BAR_TITLE", @"Title for favourites view controller")
#pragma mark - Contact Detail Communication Types
#define CONTACT_DETAIL_COMM_TYPE_EMAIL NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_EMAIL", @"")
#define CONTACT_DETAIL_COMM_TYPE_EMAIL NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_EMAIL", @"")
#define CONTACT_DETAIL_COMM_TYPE_SECURE NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_SECURE", @"")
#define CONTACT_DETAIL_COMM_TYPE_INSECURE NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_INSECURE", @"")
#define CONTACT_DETAIL_COMM_TYPE_NOTES NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_NOTES", @"")
#define CONTACT_DETAIL_COMM_TYPE_NOTES NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_NOTES", @"")
#define DIALER_CALL_BUTTON_TITLE NSLocalizedString(@"DIALER_CALL_BUTTON_TITLE", @"")
@ -83,7 +78,6 @@
#define REGISTER_CC_ERR_ALERT_VIEW_DISMISS NSLocalizedString(@"OK", @"")
#define CONTINUE_TO_WHISPER_TITLE NSLocalizedString(@"CONTINUE_TO_WHISPER_TITLE", @"")
#define REGISTER_BUTTON_TITLE NSLocalizedString(@"REGISTER_BUTTON_TITLE", @"")
#define CHALLENGE_CODE_BUTTON_TITLE NSLocalizedString(@"CHALLENGE_CODE_BUTTON_TITLE", @"")
#define END_CALL_BUTTON_TITLE NSLocalizedString(@"END_CALL_BUTTON_TITLE", @"")
@ -91,7 +85,7 @@
#define REJECT_CALL_BUTTON_TITLE NSLocalizedString(@"REJECT_CALL_BUTTON_TITLE", @"")
#define REGISTER_ERROR_ALERT_VIEW_TITLE NSLocalizedString(@"REGISTRATION_ERROR", @"")
#define REGISTER_ERROR_ALERT_VIEW_BODY NSLocalizedString(@"REGISTRATION_BODY", @"")
#define REGISTER_ERROR_ALERT_VIEW_BODY NSLocalizedString(@"REGISTRATION_BODY", @"")
#define REGISTER_ERROR_ALERT_VIEW_DISMISS NSLocalizedString(@"OK", @"")
#define REGISTER_CHALLENGE_ALERT_VIEW_TITLE NSLocalizedString(@"REGISTER_CHALLENGE_ALERT_VIEW_TITLE", @"")
@ -104,16 +98,15 @@
#pragma mark - Invite User Modal
#define INVITE_USER_MODAL_TITLE NSLocalizedString(@"INVITE_USER_MODAL_TITLE",@"")
#define INVITE_USER_MODAL_BUTTON_CANCEL NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_CANCEL",@"")
#define INVITE_USER_MODAL_BUTTON_INVITE NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_INVITE",@"")
#define INVITE_USER_MODAL_TEXT NSLocalizedString(@"INVITE_USER_MODAL_TEXT",@"")
#define INVITE_USER_MODAL_TITLE NSLocalizedString(@"INVITE_USER_MODAL_TITLE", @"")
#define INVITE_USER_MODAL_BUTTON_CANCEL NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_CANCEL", @"")
#define INVITE_USER_MODAL_BUTTON_INVITE NSLocalizedString(@"INVITE_USER_MODAL_BUTTON_INVITE", @"")
#define INVITE_USER_MODAL_TEXT NSLocalizedString(@"INVITE_USER_MODAL_TEXT", @"")
#pragma mark - Contact Intersection
#define TIMEOUT NSLocalizedString(@"ERROR_WAS_DETECTED_TITLE",@"")
#define TIMEOUT_CONTACTS_DETAIL NSLocalizedString(@"TIMEOUT_CONTACTS_DETAIL", @"")
NSDictionary* makeCallProgressLocalizedTextDictionary(void);
NSDictionary* makeCallTerminationLocalizedTextDictionary(void);
#define TIMEOUT NSLocalizedString(@"ERROR_WAS_DETECTED_TITLE", @"")
#define TIMEOUT_CONTACTS_DETAIL NSLocalizedString(@"TIMEOUT_CONTACTS_DETAIL", @"")
NSDictionary *makeCallProgressLocalizedTextDictionary(void);
NSDictionary *makeCallTerminationLocalizedTextDictionary(void);

View File

@ -1,39 +1,38 @@
#import "LocalizableText.h"
CallTermination* ct(enum CallTerminationType t);
CallTermination* ct(enum CallTerminationType t) {
CallTermination *ct(enum CallTerminationType t);
CallTermination *ct(enum CallTerminationType t) {
return [CallTermination callTerminationOfType:t withFailure:nil andMessageInfo:nil];
}
CallProgress* cp(enum CallProgressType t);
CallProgress* cp(enum CallProgressType t) {
CallProgress *cp(enum CallProgressType t);
CallProgress *cp(enum CallProgressType t) {
return [CallProgress callProgressWithType:t];
}
NSDictionary* makeCallProgressLocalizedTextDictionary(void) {
NSDictionary *makeCallProgressLocalizedTextDictionary(void) {
return @{
cp(CallProgressType_Connecting): TXT_IN_CALL_CONNECTING,
cp(CallProgressType_Ringing): TXT_IN_CALL_RINGING,
cp(CallProgressType_Securing): TXT_IN_CALL_SECURING,
cp(CallProgressType_Talking): TXT_IN_CALL_TALKING,
cp(CallProgressType_Terminated): TXT_IN_CALL_TERMINATED
};
cp(CallProgressType_Connecting) : TXT_IN_CALL_CONNECTING,
cp(CallProgressType_Ringing) : TXT_IN_CALL_RINGING,
cp(CallProgressType_Securing) : TXT_IN_CALL_SECURING,
cp(CallProgressType_Talking) : TXT_IN_CALL_TALKING,
cp(CallProgressType_Terminated) : TXT_IN_CALL_TERMINATED
};
}
NSDictionary* makeCallTerminationLocalizedTextDictionary(void) {
NSDictionary *makeCallTerminationLocalizedTextDictionary(void) {
return @{
ct(CallTerminationType_NoSuchUser): TXT_END_CALL_NO_SUCH_USER,
ct(CallTerminationType_LoginFailed): TXT_END_CALL_LOGIN_FAILED,
ct(CallTerminationType_ResponderIsBusy): TXT_END_CALL_RESPONDER_IS_BUSY,
ct(CallTerminationType_StaleSession): TXT_END_CALL_STALE_SESSION,
ct(CallTerminationType_UncategorizedFailure): TXT_END_CALL_UNCATEGORIZED_FAILURE,
ct(CallTerminationType_ReplacedByNext): TXT_END_CALL_REPLACED_BY_NEXT,
ct(CallTerminationType_RecipientUnavailable): TXT_END_CALL_RECIPIENT_UNAVAILABLE,
ct(CallTerminationType_BadInteractionWithServer): TXT_END_CALL_BAD_INTERACTION_WITH_SERVER,
ct(CallTerminationType_HandshakeFailed): TXT_END_CALL_HANDSHAKE_FAILED,
ct(CallTerminationType_HangupRemote): TXT_END_CALL_HANGUP_REMOTE,
ct(CallTerminationType_HangupLocal): TXT_END_CALL_HANGUP_LOCAL,
ct(CallTerminationType_ServerMessage): TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX,
ct(CallTerminationType_RejectedLocal): TXT_END_CALL_REJECTED_LOCAL,
ct(CallTerminationType_RejectedRemote): TXT_END_CALL_REJECTED_REMOTE
};
ct(CallTerminationType_NoSuchUser) : TXT_END_CALL_NO_SUCH_USER,
ct(CallTerminationType_LoginFailed) : TXT_END_CALL_LOGIN_FAILED,
ct(CallTerminationType_ResponderIsBusy) : TXT_END_CALL_RESPONDER_IS_BUSY,
ct(CallTerminationType_StaleSession) : TXT_END_CALL_STALE_SESSION,
ct(CallTerminationType_UncategorizedFailure) : TXT_END_CALL_UNCATEGORIZED_FAILURE,
ct(CallTerminationType_ReplacedByNext) : TXT_END_CALL_REPLACED_BY_NEXT,
ct(CallTerminationType_RecipientUnavailable) : TXT_END_CALL_RECIPIENT_UNAVAILABLE,
ct(CallTerminationType_BadInteractionWithServer) : TXT_END_CALL_BAD_INTERACTION_WITH_SERVER,
ct(CallTerminationType_HandshakeFailed) : TXT_END_CALL_HANDSHAKE_FAILED,
ct(CallTerminationType_HangupRemote) : TXT_END_CALL_HANGUP_REMOTE,
ct(CallTerminationType_HangupLocal) : TXT_END_CALL_HANGUP_LOCAL,
ct(CallTerminationType_ServerMessage) : TXT_END_CALL_MESSAGE_FROM_SERVER_PREFIX,
ct(CallTerminationType_RejectedLocal) : TXT_END_CALL_REJECTED_LOCAL,
ct(CallTerminationType_RejectedRemote) : TXT_END_CALL_REJECTED_REMOTE
};
}

View File

@ -0,0 +1,18 @@
//
// NotificationsManager.h
// Signal
//
// Created by Frederic Jacobs on 22/12/15.
// Copyright © 2015 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <TextSecureKit/NotificationsProtocol.h>
@class TSCall;
@interface NotificationsManager : NSObject <NotificationsProtocol>
- (void)notifyUserForCall:(TSCall *)call inThread:(TSThread *)thread;
@end

View File

@ -0,0 +1,150 @@
//
// NotificationsManager.m
// Signal
//
// Created by Frederic Jacobs on 22/12/15.
// Copyright © 2015 Open Whisper Systems. All rights reserved.
//
#import <AudioToolbox/AudioServices.h>
#import <TextSecureKit/TSCall.h>
#import <TextSecureKit/TSContactThread.h>
#import <TextSecureKit/TSErrorMessage.h>
#import <TextSecureKit/TSIncomingMessage.h>
#import <TextSecureKit/TextSecureKitEnv.h>
#import "Environment.h"
#import "NotificationsManager.h"
#import "PreferencesUtil.h"
#import "PushManager.h"
@interface NotificationsManager ()
@property SystemSoundID newMessageSound;
@end
@implementation NotificationsManager
- (instancetype)init {
self = [super init];
if (self) {
NSURL *newMessageSound =
[NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"NewMessage" ofType:@"aifc"]];
self.newMessageSound = AudioServicesCreateSystemSoundID((__bridge CFURLRef)newMessageSound, &_newMessageSound);
}
return self;
}
- (void)notifyUserForCall:(TSCall *)call inThread:(TSThread *)thread {
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
// Remove previous notification of call and show missed notification.
UILocalNotification *notif = [[PushManager sharedManager] closeVOIPBackgroundTask];
TSContactThread *cThread = (TSContactThread *)thread;
if (call.callType == RPRecentCallTypeMissed) {
if (notif) {
[[UIApplication sharedApplication] cancelLocalNotification:notif];
}
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.category = Signal_CallBack_Category;
notification.userInfo = @{Signal_Call_UserInfo_Key : cThread.contactIdentifier};
notification.soundName = @"NewMessage.aifc";
notification.alertBody =
[NSString stringWithFormat:NSLocalizedString(@"MSGVIEW_MISSED_CALL", nil), [thread name]];
[[PushManager sharedManager] presentNotification:notification];
}
}
}
- (void)notifyUserForErrorMessage:(TSErrorMessage *)message inThread:(TSThread *)thread {
NSString *messageDescription = message.description;
if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageDescription) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.userInfo = @{Signal_Thread_UserInfo_Key : thread.uniqueId};
notification.soundName = @"NewMessage.aifc";
NSString *alertBodyString = @"";
NSString *authorName = [thread name];
switch ([[Environment preferences] notificationPreviewType]) {
case NotificationNamePreview:
case NotificationNameNoPreview:
alertBodyString = [NSString stringWithFormat:@"%@: %@", authorName, messageDescription];
break;
case NotificationNoNameNoPreview:
alertBodyString = messageDescription;
break;
}
notification.alertBody = alertBodyString;
[[PushManager sharedManager] presentNotification:notification];
} else {
if ([Environment.preferences soundInForeground]) {
AudioServicesPlayAlertSound(_newMessageSound);
}
}
}
- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)message from:(NSString *)name inThread:(TSThread *)thread {
NSString *messageDescription = message.description;
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && messageDescription) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.soundName = @"NewMessage.aifc";
switch ([[Environment preferences] notificationPreviewType]) {
case NotificationNamePreview:
notification.category = Signal_Full_New_Message_Category;
notification.userInfo =
@{Signal_Thread_UserInfo_Key : thread.uniqueId, Signal_Message_UserInfo_Key : message.uniqueId};
if ([thread isGroupThread]) {
NSString *sender =
[[TextSecureKitEnv sharedEnv].contactsManager nameStringForPhoneIdentifier:message.authorId];
if (!sender) {
sender = message.authorId;
}
NSString *threadName = [NSString stringWithFormat:@"\"%@\"", name];
notification.alertBody =
[NSString stringWithFormat:NSLocalizedString(@"APN_MESSAGE_IN_GROUP_DETAILED", nil),
sender,
threadName,
messageDescription];
} else {
notification.alertBody = [NSString stringWithFormat:@"%@: %@", name, messageDescription];
}
break;
case NotificationNameNoPreview: {
notification.userInfo = @{Signal_Thread_UserInfo_Key : thread.uniqueId};
if ([thread isGroupThread]) {
notification.alertBody =
[NSString stringWithFormat:@"%@ \"%@\"", NSLocalizedString(@"APN_MESSAGE_IN_GROUP", nil), name];
} else {
notification.alertBody =
[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"APN_MESSAGE_FROM", nil), name];
}
break;
}
case NotificationNoNameNoPreview:
notification.alertBody = NSLocalizedString(@"APN_Message", nil);
break;
default:
notification.alertBody = NSLocalizedString(@"APN_Message", nil);
break;
}
[[PushManager sharedManager] presentNotification:notification];
} else {
if ([Environment.preferences soundInForeground]) {
AudioServicesPlayAlertSound(_newMessageSound);
}
}
}
@end

View File

@ -19,14 +19,14 @@ typedef NS_ENUM(NSUInteger, TSImageQuality) {
@interface PropertyListPreferences (PropertyUtil)
- (NSTimeInterval) getCachedOrDefaultDesiredBufferDepth;
- (void) setCachedDesiredBufferDepth:(double)value;
- (NSTimeInterval)getCachedOrDefaultDesiredBufferDepth;
- (void)setCachedDesiredBufferDepth:(double)value;
- (BOOL) getHasSentAMessage;
- (void) setHasSentAMessage:(BOOL)enabled;
- (BOOL)getHasSentAMessage;
- (void)setHasSentAMessage:(BOOL)enabled;
- (BOOL) getHasArchivedAMessage;
- (void) setHasArchivedAMessage:(BOOL)enabled;
- (BOOL)getHasArchivedAMessage;
- (void)setHasArchivedAMessage:(BOOL)enabled;
- (BOOL)loggingIsEnabled;
- (void)setLoggingEnabled:(BOOL)flag;
@ -36,19 +36,19 @@ typedef NS_ENUM(NSUInteger, TSImageQuality) {
- (NotificationType)notificationPreviewType;
- (void)setNotificationPreviewType:(NotificationType)type;
- (NSString*)nameForNotificationPreviewType:(NotificationType)notificationType;
- (NSString *)nameForNotificationPreviewType:(NotificationType)notificationType;
- (BOOL)soundInForeground;
- (void)setSoundInForeground:(BOOL)enabled;
- (BOOL)hasRegisteredVOIPPush;
-(void)setHasRegisteredVOIPPush:(BOOL)enabled;
- (void)setHasRegisteredVOIPPush:(BOOL)enabled;
- (TSImageQuality)imageUploadQuality;
- (void)setImageUploadQuality:(TSImageQuality)quality;
- (NSString*)lastRanVersion;
- (NSString*)setAndGetCurrentVersion;
- (NSString *)lastRanVersion;
- (NSString *)setAndGetCurrentVersion;
@end

View File

@ -1,5 +1,5 @@
#import "PreferencesUtil.h"
#import "Constraints.h"
#import "PreferencesUtil.h"
#define CALL_STREAM_DES_BUFFER_LEVEL_KEY @"CallStreamDesiredBufferLevel"
@ -25,17 +25,18 @@
@implementation PropertyListPreferences (PropertyUtil)
-(NSTimeInterval) getCachedOrDefaultDesiredBufferDepth {
- (NSTimeInterval)getCachedOrDefaultDesiredBufferDepth {
id v = [self tryGetValueForKey:CALL_STREAM_DES_BUFFER_LEVEL_KEY];
if (v == nil) return DEFAULT_CALL_STREAM_DES_BUFFER_LEVEL;
if (v == nil)
return DEFAULT_CALL_STREAM_DES_BUFFER_LEVEL;
return [v doubleValue];
}
-(void) setCachedDesiredBufferDepth:(double)value {
require(value >= 0);
- (void)setCachedDesiredBufferDepth:(double)value {
ows_require(value >= 0);
[self setValueForKey:CALL_STREAM_DES_BUFFER_LEVEL_KEY toValue:@(value)];
}
-(BOOL) getFreshInstallTutorialsEnabled {
- (BOOL)getFreshInstallTutorialsEnabled {
NSNumber *preference = [self tryGetValueForKey:FRESH_INSTALL_TUTORIALS_ENABLED_KEY];
if (preference) {
return [preference boolValue];
@ -43,7 +44,7 @@
return YES;
}
}
-(BOOL) getContactImagesEnabled {
- (BOOL)getContactImagesEnabled {
NSNumber *preference = [self tryGetValueForKey:CONTACT_IMAGES_ENABLED_KEY];
if (preference) {
return [preference boolValue];
@ -51,7 +52,7 @@
return YES;
}
}
-(BOOL) getAutocorrectEnabled {
- (BOOL)getAutocorrectEnabled {
NSNumber *preference = [self tryGetValueForKey:AUTOCORRECT_ENABLED_KEY];
if (preference) {
return [preference boolValue];
@ -59,7 +60,7 @@
return YES;
}
}
-(BOOL) getHistoryLogEnabled {
- (BOOL)getHistoryLogEnabled {
NSNumber *preference = [self tryGetValueForKey:HISTORY_LOG_ENABLED_KEY];
if (preference) {
return [preference boolValue];
@ -68,110 +69,108 @@
}
}
- (BOOL)loggingIsEnabled{
- (BOOL)loggingIsEnabled {
NSNumber *preference = [self tryGetValueForKey:DEBUG_IS_ENABLED_KEY];
if (preference) {
return [preference boolValue];
} else{
} else {
return YES;
}
}
-(BOOL)screenSecurityIsEnabled{
- (BOOL)screenSecurityIsEnabled {
NSNumber *preference = [self tryGetValueForKey:SCREEN_SECURITY_KEY];
if (preference) {
return [preference boolValue];
} else{
} else {
return NO;
}
}
- (BOOL) getHasSentAMessage{
- (BOOL)getHasSentAMessage {
NSNumber *preference = [self tryGetValueForKey:HAS_SENT_A_MESSAGE_KEY];
if (preference) {
return [preference boolValue];
} else{
} else {
return NO;
}
}
- (BOOL) getHasArchivedAMessage {
- (BOOL)getHasArchivedAMessage {
NSNumber *preference = [self tryGetValueForKey:HAS_ARCHIVED_A_MESSAGE_KEY];
if (preference) {
return [preference boolValue];
} else{
} else {
return NO;
}
}
- (BOOL)hasRegisteredVOIPPush {
NSNumber *preference = [self tryGetValueForKey:HAS_REGISTERED_VOIP_PUSH];
if (preference) {
return [preference boolValue];
} else{
} else {
return YES;
}
}
-(TSImageQuality)imageUploadQuality {
- (TSImageQuality)imageUploadQuality {
// always return average image quality
return TSImageQualityMedium;
}
-(void)setImageUploadQuality:(TSImageQuality)quality {
- (void)setImageUploadQuality:(TSImageQuality)quality {
[self setValueForKey:IMAGE_UPLOAD_QUALITY_KEY toValue:@(quality)];
}
-(void)setScreenSecurity:(BOOL)flag{
- (void)setScreenSecurity:(BOOL)flag {
[self setValueForKey:SCREEN_SECURITY_KEY toValue:@(flag)];
}
-(void) setFreshInstallTutorialsEnabled:(BOOL)enabled {
- (void)setFreshInstallTutorialsEnabled:(BOOL)enabled {
[self setValueForKey:FRESH_INSTALL_TUTORIALS_ENABLED_KEY toValue:@(enabled)];
}
-(void)setHasRegisteredVOIPPush:(BOOL)enabled {
- (void)setHasRegisteredVOIPPush:(BOOL)enabled {
[self setValueForKey:HAS_REGISTERED_VOIP_PUSH toValue:@(enabled)];
}
-(void) setContactImagesEnabled:(BOOL)enabled {
- (void)setContactImagesEnabled:(BOOL)enabled {
[self setValueForKey:CONTACT_IMAGES_ENABLED_KEY toValue:@(enabled)];
}
-(void) setAutocorrectEnabled:(BOOL)enabled {
- (void)setAutocorrectEnabled:(BOOL)enabled {
[self setValueForKey:AUTOCORRECT_ENABLED_KEY toValue:@(enabled)];
}
-(void) setHistoryLogEnabled:(BOOL)enabled {
- (void)setHistoryLogEnabled:(BOOL)enabled {
[self setValueForKey:HISTORY_LOG_ENABLED_KEY toValue:@(enabled)];
}
-(BOOL) encounteredRevokedPushPermission{
- (BOOL)encounteredRevokedPushPermission {
return [[self tryGetValueForKey:PUSH_REVOKED_KEY] boolValue];
}
-(void) setRevokedPushPermission:(BOOL)revoked{
- (void)setRevokedPushPermission:(BOOL)revoked {
[self setValueForKey:PUSH_REVOKED_KEY toValue:@(revoked)];
}
-(void) setLoggingEnabled:(BOOL)flag{
- (void)setLoggingEnabled:(BOOL)flag {
[self setValueForKey:DEBUG_IS_ENABLED_KEY toValue:@(flag)];
}
-(NSString*)lastRanVersion{
- (NSString *)lastRanVersion {
return [NSUserDefaults.standardUserDefaults objectForKey:kSignalVersionKey];
}
- (void) setHasSentAMessage:(BOOL)enabled{
- (void)setHasSentAMessage:(BOOL)enabled {
[self setValueForKey:HAS_SENT_A_MESSAGE_KEY toValue:@(enabled)];
}
- (void) setHasArchivedAMessage:(BOOL)enabled{
- (void)setHasArchivedAMessage:(BOOL)enabled {
[self setValueForKey:HAS_ARCHIVED_A_MESSAGE_KEY toValue:@(enabled)];
}
-(NSString*)setAndGetCurrentVersion{
NSString *currentVersion = [NSString stringWithFormat:@"%@", NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]];
- (NSString *)setAndGetCurrentVersion {
NSString *currentVersion =
[NSString stringWithFormat:@"%@", NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]];
[NSUserDefaults.standardUserDefaults setObject:currentVersion forKey:kSignalVersionKey];
[NSUserDefaults.standardUserDefaults synchronize];
return currentVersion;
@ -183,7 +182,7 @@
NSNumber *preference = [self tryGetValueForKey:PLAY_SOUND_IN_FOREGROUND_KEY];
if (preference) {
return [preference boolValue];
} else{
} else {
return YES;
}
}
@ -192,14 +191,13 @@
[self setValueForKey:PLAY_SOUND_IN_FOREGROUND_KEY toValue:@(enabled)];
}
-(void)setNotificationPreviewType:(NotificationType)type
{
- (void)setNotificationPreviewType:(NotificationType)type {
[self setValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY toValue:@(type)];
}
-(NotificationType)notificationPreviewType {
- (NotificationType)notificationPreviewType {
NSNumber *preference = [self tryGetValueForKey:NOTIFICATION_PREVIEW_TYPE_KEY];
if (preference) {
return [preference unsignedIntegerValue];
} else {
@ -207,7 +205,7 @@
}
}
- (NSString*)nameForNotificationPreviewType:(NotificationType)notificationType {
- (NSString *)nameForNotificationPreviewType:(NotificationType)notificationType {
switch (notificationType) {
case NotificationNamePreview:
return NSLocalizedString(@"NOTIFICATIONS_SENDER_AND_MESSAGE", nil);

View File

@ -2,9 +2,9 @@
@interface PropertyListPreferences : NSObject
-(id) tryGetValueForKey:(NSString*)key;
-(void) setValueForKey:(NSString*)key toValue:(id)value;
-(id) adjustAndTryGetNewValueForKey:(NSString*)key afterAdjuster:(id (^)(id oldValue))adjuster;
-(void) clear;
- (id)tryGetValueForKey:(NSString *)key;
- (void)setValueForKey:(NSString *)key toValue:(id)value;
- (id)adjustAndTryGetNewValueForKey:(NSString *)key afterAdjuster:(id (^)(id oldValue))adjuster;
- (void)clear;
@end

View File

@ -1,5 +1,5 @@
#import "PropertyListPreferences.h"
#import "Constraints.h"
#import "PropertyListPreferences.h"
#import "TSStorageHeaders.h"
#define SignalDatabaseCollection @"SignalPreferences"
@ -7,7 +7,7 @@
@implementation PropertyListPreferences
-(void) clear {
- (void)clear {
@synchronized(self) {
NSString *appDomain = NSBundle.mainBundle.bundleIdentifier;
[NSUserDefaults.standardUserDefaults removePersistentDomainForName:appDomain];
@ -15,17 +15,17 @@
}
- (id)tryGetValueForKey:(NSString *)key {
require(key != nil);
ows_require(key != nil);
return [TSStorageManager.sharedManager objectForKey:key inCollection:SignalDatabaseCollection];
}
- (void)setValueForKey:(NSString *)key toValue:(id)value {
require(key != nil);
ows_require(key != nil);
[TSStorageManager.sharedManager setObject:value forKey:key inCollection:SignalDatabaseCollection];
}
- (id)adjustAndTryGetNewValueForKey:(NSString *)key afterAdjuster:(id (^)(id))adjuster {
require(key != nil);
require(adjuster != nil);
ows_require(key != nil);
ows_require(adjuster != nil);
@synchronized(self) {
id oldValue = [self tryGetValueForKey:key];
id newValue = adjuster(oldValue);

View File

@ -1,18 +1,18 @@
#import <Foundation/Foundation.h>
#import "Environment.h"
#import "DH3KKeyAgreementProtocol.h"
#import "EC25KeyAgreementProtocol.h"
#import "Environment.h"
@interface Release : NSObject
/// Connects to actual production infrastructure
+(Environment*) releaseEnvironmentWithLogging:(id<Logging>)logging;
+ (Environment *)releaseEnvironmentWithLogging:(id<Logging>)logging;
+(Environment*) stagingEnvironmentWithLogging:(id<Logging>)logging;
+ (Environment *)stagingEnvironmentWithLogging:(id<Logging>)logging;
/// Fake environment with no logging
+(Environment*) unitTestEnvironment:(NSArray*)testingAndLegacyOptions;
+ (Environment *)unitTestEnvironment:(NSArray *)testingAndLegacyOptions;
+(DH3KKeyAgreementProtocol*) supportedDH3KKeyAgreementProtocol;
+ (DH3KKeyAgreementProtocol *)supportedDH3KKeyAgreementProtocol;
@end

View File

@ -1,8 +1,8 @@
#import "Release.h"
#import "DiscardingLog.h"
#import "PhoneManager.h"
#import "PhoneNumberUtil.h"
#import "RecentCallManager.h"
#import "Release.h"
#define RELEASE_ZRTP_CLIENT_ID @"Whisper 000 ".encodedAsAscii
#define RELEASE_ZRTP_VERSION_ID @"1.10".encodedAsAscii
@ -10,121 +10,95 @@
#define TESTING_ZRTP_CLIENT_ID @"RedPhone 019 ".encodedAsAscii
#define TESTING_ZRTP_VERSION_ID @"1.10".encodedAsAscii
static unsigned char DH3K_PRIME[]={
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,
0x34,0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1,0x29,0x02,0x4E,0x08,0x8A,0x67,
0xCC,0x74,0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,
0x34,0x04,0xDD,0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D,
0xF2,0x5F,0x14,0x37,0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45,0xE4,0x85,0xB5,
0x76,0x62,0x5E,0x7E,0xC6,0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B,0x0B,0xFF,
0x5C,0xB6,0xF4,0x06,0xB7,0xED,0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,
0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D,
0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,
0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,0x83,0x65,0x5D,0x23,0xDC,0xA3,
0xAD,0x96,0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,0x9E,0xD5,0x29,0x07,0x70,
0x96,0x96,0x6D,0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,0xF1,0x74,0x6C,0x08,
0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,0xE3,0x9E,0x77,
0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,0xB5,0xC5,
0x5D,0xF0,0x6F,0x4C,0x52,0xC9,0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,0x39,
0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D,0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A,
0x33,0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64,0xEC,0xFB,0x85,0x04,0x58,0xDB,
0xEF,0x0A,0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D,0xB3,0x97,0x0F,0x85,0xA6,
0xE1,0xE4,0xC7,0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7,0x1E,0x8C,0x94,0xE0,
0x4A,0x25,0x61,0x9D,0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B,0xF1,0x2F,0xFA,
0x06,0xD9,0x8A,0x08,0x64,0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64,0x52,0x1F,
0x2B,0x18,0x17,0x7B,0x20,0x0C,0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C,0x77,
0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31,
0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,0x4B,0x82,0xD1,0x20,0xA9,0x3A,0xD2,
0xCA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
static unsigned char DH3K_PRIME[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62,
0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13,
0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30,
0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76,
0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7,
0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28,
0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, 0x1C,
0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96,
0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35,
0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36,
0xCE, 0x3B, 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, 0xB5,
0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C,
0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4,
0x2D, 0xAD, 0x33, 0x17, 0x0D, 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, 0xEC, 0xFB,
0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6,
0xE1, 0xE4, 0xC7, 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, 0x4A, 0x25, 0x61, 0x9D,
0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02,
0x73, 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61,
0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, 0x43,
0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, 0xA9, 0x3A, 0xD2, 0xCA, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF};
#define DH3K_GENERATOR 2
@implementation Release
+(Environment*) releaseEnvironmentWithLogging:(id<Logging>)logging {
//ErrorHandlerBlock errorDiscarder = ^(id error, id relatedInfo, bool causedTermination) {};
ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) {DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination); };
NSString *defaultRegion;
#if TARGET_OS_IPHONE
defaultRegion = [[PhoneNumberUtil sharedInstance].nbPhoneNumberUtil countryCodeByCarrier];
if ([defaultRegion isEqualToString:@"ZZ"]) {
defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
}
#else
defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
#endif
NSAssert([defaultRegion isKindOfClass:[NSString class]], @"");
return [Environment environmentWithLogging:logging
andErrorNoter:errorNoter
andServerPort:31337
andMasterServerHostName:@"master.whispersystems.org"
andDefaultRelayName:@"relay"
andRelayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andCurrentRegionCodeForPhoneNumbers:defaultRegion
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andRecentCallManager:[RecentCallManager new]
andTestingAndLegacyOptions:@[ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID
andContactsManager:[ContactsManager new]];
+ (Environment *)releaseEnvironmentWithLogging:(id<Logging>)logging {
// ErrorHandlerBlock errorDiscarder = ^(id error, id relatedInfo, bool causedTermination) {};
ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) {
DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination);
};
return [Environment
environmentWithLogging:logging
andErrorNoter:errorNoter
andServerPort:31337
andMasterServerHostName:@"master.whispersystems.org"
andDefaultRelayName:@"relay"
andRelayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andRecentCallManager:[RecentCallManager new]
andTestingAndLegacyOptions:@[
ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER
]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID
andContactsManager:[ContactsManager new]];
}
+(Environment*) stagingEnvironmentWithLogging:(id<Logging>)logging {
ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) {DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination); };
NSString *defaultRegion;
#if TARGET_OS_IPHONE
defaultRegion = [[PhoneNumberUtil sharedInstance].nbPhoneNumberUtil countryCodeByCarrier];
if ([defaultRegion isEqualToString:@"ZZ"]) {
defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
}
#else
defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
#endif
NSAssert([defaultRegion isKindOfClass:[NSString class]], @"");
return [Environment environmentWithLogging:logging
andErrorNoter:errorNoter
andServerPort:31337
andMasterServerHostName:@"redphone-staging.whispersystems.org"
andDefaultRelayName:@"redphone-staging-relay"
andRelayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andCurrentRegionCodeForPhoneNumbers:defaultRegion
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andRecentCallManager:[RecentCallManager new]
andTestingAndLegacyOptions:@[ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID
andContactsManager:[ContactsManager new]];
+ (Environment *)stagingEnvironmentWithLogging:(id<Logging>)logging {
ErrorHandlerBlock errorNoter = ^(id error, id relatedInfo, bool causedTermination) {
DDLogError(@"%@: %@, %d", error, relatedInfo, causedTermination);
};
return [Environment
environmentWithLogging:logging
andErrorNoter:errorNoter
andServerPort:31337
andMasterServerHostName:@"redphone-staging.whispersystems.org"
andDefaultRelayName:@"redphone-staging-relay"
andRelayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols]
andPhoneManager:[PhoneManager phoneManagerWithErrorHandler:errorNoter]
andRecentCallManager:[RecentCallManager new]
andTestingAndLegacyOptions:@[
ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER
]
andZrtpClientId:RELEASE_ZRTP_CLIENT_ID
andZrtpVersionId:RELEASE_ZRTP_VERSION_ID
andContactsManager:[ContactsManager new]];
}
+(Environment*) unitTestEnvironment:(NSArray*)testingAndLegacyOptions {
NSArray* keyAgreementProtocols = self.supportedKeyAgreementProtocols;
+ (Environment *)unitTestEnvironment:(NSArray *)testingAndLegacyOptions {
NSArray *keyAgreementProtocols = self.supportedKeyAgreementProtocols;
if ([testingAndLegacyOptions containsObject:TESTING_OPTION_USE_DH_FOR_HANDSHAKE]) {
keyAgreementProtocols = @[[Release supportedDH3KKeyAgreementProtocol]];
keyAgreementProtocols = @[ [Release supportedDH3KKeyAgreementProtocol] ];
}
return [Environment environmentWithLogging:[DiscardingLog discardingLog]
andErrorNoter:^(id error, id relatedInfo, bool causedTermination) {}
andErrorNoter:^(id error, id relatedInfo, bool causedTermination) {
}
andServerPort:31337
andMasterServerHostName:@"master.whispersystems.org"
andDefaultRelayName:@"relay"
andRelayServerHostNameSuffix:@"whispersystems.org"
andCertificate:[Certificate certificateFromResourcePath:@"redphone" ofType:@"cer"]
andCurrentRegionCodeForPhoneNumbers:@"US"
andSupportedKeyAgreementProtocols:keyAgreementProtocols
andPhoneManager:nil
andRecentCallManager:nil
@ -134,16 +108,13 @@ static unsigned char DH3K_PRIME[]={
andContactsManager:nil];
}
+(NSArray*) supportedKeyAgreementProtocols {
return @[
[EC25KeyAgreementProtocol new],
[Release supportedDH3KKeyAgreementProtocol]
];
+ (NSArray *)supportedKeyAgreementProtocols {
return @[ [EC25KeyAgreementProtocol new], [Release supportedDH3KKeyAgreementProtocol] ];
}
+(DH3KKeyAgreementProtocol*) supportedDH3KKeyAgreementProtocol {
NSData* prime = [NSData dataWithBytes:DH3K_PRIME length:sizeof(DH3K_PRIME)];
NSData* generator = [NSData dataWithSingleByte:DH3K_GENERATOR];
+ (DH3KKeyAgreementProtocol *)supportedDH3KKeyAgreementProtocol {
NSData *prime = [NSData dataWithBytes:DH3K_PRIME length:sizeof(DH3K_PRIME)];
NSData *generator = [NSData dataWithSingleByte:DH3K_GENERATOR];
return [DH3KKeyAgreementProtocol protocolWithModulus:prime andGenerator:generator];
}

View File

@ -15,44 +15,34 @@
#define SAVED_PASSWORD_KEY @"Password"
#define SIGNALING_MAC_KEY @"Signaling Mac Key"
#define SIGNALING_CIPHER_KEY @"Signaling Cipher Key"
#define ZID_KEY @"ZID"
#define ZID_LENGTH 12
#define SIGNALING_EXTRA_KEY @"Signaling Extra Key"
@interface SignalKeyingStorage : NSObject
+(void)generateSignaling;
+(void)generateServerAuthPassword;
#pragma mark Registered Phone Number
+(PhoneNumber*)localNumber;
+(void)setLocalNumberTo:(PhoneNumber*)localNumber;
+ (void)generateSignaling;
+ (void)generateServerAuthPassword;
#pragma mark Signaling Key
+(int64_t)getAndIncrementOneTimeCounter;
#pragma mark Zid
+(Zid*)zid;
+ (int64_t)getAndIncrementOneTimeCounter;
#pragma mark Server Auth
+(NSString*)serverAuthPassword;
+ (NSString *)serverAuthPassword;
#pragma mark Signaling
+(NSData*)signalingMacKey;
+(NSData*)signalingCipherKey;
+ (NSData *)signalingMacKey;
+ (NSData *)signalingCipherKey;
/**
* Returns the extra keying material generated at registration.
Warning: Users of older versions of Signal (<= 2.1.1) might have the signaling cipher key as extra keing material.
* Returns the extra keying material generated at registration.
Warning: Users of older versions of Signal (<= 2.1.1) might have the signaling cipher key as extra keing
material.
*
* @return Extra keying material from registration time
*/
+(NSData*)signalingExtraKey;
+ (NSData *)signalingExtraKey;
@end

View File

@ -5,111 +5,88 @@
// Created by Frederic Jacobs on 09/07/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import "Constraints.h"
#import "CryptoTools.h"
#import "SignalKeyingStorage.h"
#import "Constraints.h"
#import "TSStorageManager.h"
#import "Util.h"
#define SignalKeyingCollection @"SignalKeyingCollection"
#define SIGNALING_MAC_KEY_LENGTH 20
#define SIGNALING_MAC_KEY_LENGTH 20
#define SIGNALING_CIPHER_KEY_LENGTH 16
#define SAVED_PASSWORD_LENGTH 18
#define SIGNALING_EXTRA_KEY_LENGTH 4
@implementation SignalKeyingStorage
+ (void)generateServerAuthPassword{
[self storeString:[[CryptoTools generateSecureRandomData:SAVED_PASSWORD_LENGTH] encodedAsBase64] forKey:SAVED_PASSWORD_KEY];
+ (void)generateServerAuthPassword {
[self storeString:[[CryptoTools generateSecureRandomData:SAVED_PASSWORD_LENGTH] encodedAsBase64]
forKey:SAVED_PASSWORD_KEY];
}
+ (void)generateSignaling{
+ (void)generateSignaling {
[self storeData:[CryptoTools generateSecureRandomData:SIGNALING_MAC_KEY_LENGTH] forKey:SIGNALING_MAC_KEY];
[self storeData:[CryptoTools generateSecureRandomData:SIGNALING_CIPHER_KEY_LENGTH] forKey:SIGNALING_CIPHER_KEY];
[self storeData:[CryptoTools generateSecureRandomData:SIGNALING_EXTRA_KEY_LENGTH] forKey:SIGNALING_EXTRA_KEY];
[self storeData:[CryptoTools generateSecureRandomData:ZID_LENGTH] forKey:ZID_KEY];
}
+(int64_t) getAndIncrementOneTimeCounter {
+ (int64_t)getAndIncrementOneTimeCounter {
__block int64_t oldCounter;
oldCounter = [[self stringForKey:PASSWORD_COUNTER_KEY] longLongValue];
int64_t newCounter = (oldCounter == INT64_MAX)?INT64_MIN:(oldCounter + 1);
oldCounter = [[self stringForKey:PASSWORD_COUNTER_KEY] longLongValue];
int64_t newCounter = (oldCounter == INT64_MAX) ? INT64_MIN : (oldCounter + 1);
[self storeString:[@(newCounter) stringValue] forKey:PASSWORD_COUNTER_KEY];
return newCounter;
}
+ (void)setLocalNumberTo:(PhoneNumber *)localNumber{
require(localNumber != nil);
require(localNumber.toE164!= nil);
NSString *e164 = localNumber.toE164;
[self storeString:e164 forKey:LOCAL_NUMBER_KEY];
}
+ (PhoneNumber *)localNumber{
NSString *lnString = [self stringForKey:LOCAL_NUMBER_KEY];
checkOperation(lnString != nil );
PhoneNumber *num = [PhoneNumber tryParsePhoneNumberFromE164:lnString];
return lnString?num:nil;
}
+(Zid *)zid{
NSData *data = [self dataForKey:ZID_KEY];
if (data.length != ZID_LENGTH) {
DDLogError(@"ZID length is incorrect. Is %lu, should be %d", (unsigned long)data.length, ZID_LENGTH);
}
Zid *zid = [Zid zidWithData:data];
return zid;
}
+(NSData *)signalingCipherKey{
+ (NSData *)signalingCipherKey {
return [self dataForKey:SIGNALING_CIPHER_KEY andVerifyLength:SIGNALING_CIPHER_KEY_LENGTH];
}
+(NSData *)signalingMacKey{
+ (NSData *)signalingMacKey {
return [self dataForKey:SIGNALING_MAC_KEY andVerifyLength:SIGNALING_MAC_KEY_LENGTH];
}
+ (NSData *)signalingExtraKey{
+ (NSData *)signalingExtraKey {
return [self dataForKey:SIGNALING_EXTRA_KEY andVerifyLength:SIGNALING_EXTRA_KEY_LENGTH];
}
+(NSString *)serverAuthPassword{
+ (NSString *)serverAuthPassword {
NSString *password = [self stringForKey:SAVED_PASSWORD_KEY];
NSData *data = [password decodedAsBase64Data];
NSData *data = [password decodedAsBase64Data];
if (data.length != SAVED_PASSWORD_LENGTH) {
DDLogError(@"The server password has incorrect length. Is %lu but should be %d", (unsigned long)data.length, SAVED_PASSWORD_LENGTH);
DDLogError(@"The server password has incorrect length. Is %lu but should be %d",
(unsigned long)data.length,
SAVED_PASSWORD_LENGTH);
}
return password;
}
#pragma mark Keychain wrapper methods
+(void)storeData:(NSData*)data forKey:(NSString*)key{
+ (void)storeData:(NSData *)data forKey:(NSString *)key {
[TSStorageManager.sharedManager setObject:data forKey:key inCollection:SignalKeyingCollection];
}
+(NSData*)dataForKey:(NSString*)key andVerifyLength:(uint)length{
+ (NSData *)dataForKey:(NSString *)key andVerifyLength:(uint)length {
NSData *data = [self dataForKey:key];
if (data.length != length) {
DDLogError(@"Length of data not matching. Got %lu, expected %u", (unsigned long)data.length, length);
}
return data;
}
+(NSData*)dataForKey:(NSString*)key{
+ (NSData *)dataForKey:(NSString *)key {
return [TSStorageManager.sharedManager dataForKey:key inCollection:SignalKeyingCollection];
}
+(NSString*)stringForKey:(NSString*)key{
+ (NSString *)stringForKey:(NSString *)key {
return [TSStorageManager.sharedManager stringForKey:key inCollection:SignalKeyingCollection];
}
+(void)storeString:(NSString*)string forKey:(NSString*)key{
+ (void)storeString:(NSString *)string forKey:(NSString *)key {
[TSStorageManager.sharedManager setObject:string forKey:key inCollection:SignalKeyingCollection];
}

View File

@ -12,124 +12,134 @@
#import "LockInteractionController.h"
#import "PreferencesUtil.h"
#import "PushManager.h"
#import "TSAccountManager.h"
#import "TSNetworkManager.h"
#import "RecentCallManager.h"
#import "SignalKeyingStorage.h"
#import "TSAccountManager.h"
#import "TSNetworkManager.h"
#define NEEDS_TO_REGISTER_PUSH_KEY @"Register For Push"
#define NEEDS_TO_REGISTER_PUSH_KEY @"Register For Push"
#define NEEDS_TO_REGISTER_ATTRIBUTES @"Register Attributes"
@interface SignalKeyingStorage(VersionMigrations)
@interface SignalKeyingStorage (VersionMigrations)
+(void)storeString:(NSString*)string forKey:(NSString*)key;
+(void)storeData:(NSData*)data forKey:(NSString*)key;
+ (void)storeString:(NSString *)string forKey:(NSString *)key;
+ (void)storeData:(NSData *)data forKey:(NSString *)key;
@end
@implementation VersionMigrations
#pragma mark Utility methods
+ (void)performUpdateCheck{
NSString *previousVersion = Environment.preferences.lastRanVersion;
NSString *currentVersion = [Environment.preferences setAndGetCurrentVersion];
BOOL VOIPRegistration = [[PushManager sharedManager] supportsVOIPPush]
&& ![Environment.preferences hasRegisteredVOIPPush];
+ (void)performUpdateCheck {
NSString *previousVersion = Environment.preferences.lastRanVersion;
NSString *currentVersion = [Environment.preferences setAndGetCurrentVersion];
BOOL VOIPRegistration =
[[PushManager sharedManager] supportsVOIPPush] && ![Environment.preferences hasRegisteredVOIPPush];
if (!previousVersion) {
DDLogError(@"No previous version found. Possibly first launch since install.");
return;
}
if(([self isVersion:previousVersion atLeast:@"1.0.2" andLessThan:@"2.0"])) {
if (([self isVersion:previousVersion atLeast:@"1.0.2" andLessThan:@"2.0"])) {
// We don't migrate from RedPhone anymore, too painful to maintain.
// Resetting the app data and quitting.
[Environment resetAppData];
exit(0);
}
// VOIP Push might need to be enabled because 1) user ran old version 2) Update to compatible iOS version
if (VOIPRegistration && [TSAccountManager isRegistered]) {
[self nonBlockingPushRegistration];
}
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.1.70"] && [TSAccountManager isRegistered]) {
[self clearVideoCache];
[self blockingAttributesUpdate];
}
if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.3.0"] && [TSAccountManager isRegistered]) {
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *cachesDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *bloomFilterPath = [[cachesDir objectAtIndex:0] stringByAppendingPathComponent:@"bloomfilter"];
[fm removeItemAtPath:bloomFilterPath error:nil];
[[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
[transaction removeAllObjectsInCollection:@"TSRecipient"];
NSArray *cachesDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *bloomFilterPath = [[cachesDir objectAtIndex:0] stringByAppendingPathComponent:@"bloomfilter"];
[fm removeItemAtPath:bloomFilterPath error:nil];
[[TSStorageManager sharedManager]
.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[transaction removeAllObjectsInCollection:@"TSRecipient"];
}];
}
}
+ (BOOL) isVersion:(NSString *)thisVersionString atLeast:(NSString *)openLowerBoundVersionString andLessThan:(NSString *)closedUpperBoundVersionString {
return [self isVersion:thisVersionString atLeast:openLowerBoundVersionString] && [self isVersion:thisVersionString lessThan:closedUpperBoundVersionString];
+ (BOOL)isVersion:(NSString *)thisVersionString
atLeast:(NSString *)openLowerBoundVersionString
andLessThan:(NSString *)closedUpperBoundVersionString {
return [self isVersion:thisVersionString atLeast:openLowerBoundVersionString] &&
[self isVersion:thisVersionString lessThan:closedUpperBoundVersionString];
}
+ (BOOL) isVersion:(NSString *)thisVersionString atLeast:(NSString *)thatVersionString {
+ (BOOL)isVersion:(NSString *)thisVersionString atLeast:(NSString *)thatVersionString {
return [thisVersionString compare:thatVersionString options:NSNumericSearch] != NSOrderedAscending;
}
+ (BOOL) isVersion:(NSString *)thisVersionString lessThan:(NSString *)thatVersionString {
+ (BOOL)isVersion:(NSString *)thisVersionString lessThan:(NSString *)thatVersionString {
return [thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedAscending;
}
#pragma mark Upgrading to 2.1 - Needs to register VOIP token + Removing video cache folder
+ (void)nonBlockingPushRegistration{
__block failedVerificationBlock failedBlock = ^(NSError *error) {
DDLogError(@"Failed to register VOIP push token: %@", error.debugDescription);
+ (void)nonBlockingPushRegistration {
__block failedBlock failedBlock = ^(NSError *error) {
DDLogError(@"Failed to register VOIP push token: %@", error.debugDescription);
};
[[PushManager sharedManager] requestPushTokenWithSuccess:^(NSData *pushToken, NSData *voipToken) {
[TSAccountManager registerForPushNotifications:pushToken voipToken:voipToken success:^{
DDLogWarn(@"Registered for VOIP Push.");
} failure:failedBlock];
} failure:failedBlock];
[[PushManager sharedManager] requestPushTokenWithSuccess:^(NSString *pushToken, NSString *voipToken) {
[TSAccountManager registerForPushNotifications:pushToken
voipToken:voipToken
success:^{
DDLogWarn(@"Registered for VOIP Push.");
}
failure:failedBlock];
}
failure:failedBlock];
}
+ (void)blockingPushRegistration{
LIControllerBlockingOperation blockingOperation = ^BOOL(void){
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:NEEDS_TO_REGISTER_PUSH_KEY];
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL success;
__block failedVerificationBlock failedBlock = ^(NSError *error) {
success = NO;
dispatch_semaphore_signal(sema);
};
[[PushManager sharedManager] requestPushTokenWithSuccess:^(NSData *pushToken, NSData *voipToken) {
[TSAccountManager registerForPushNotifications:pushToken voipToken:voipToken success:^{
success = YES;
dispatch_semaphore_signal(sema);
} failure:failedBlock];
} failure:failedBlock];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return success;
+ (void)blockingPushRegistration {
LIControllerBlockingOperation blockingOperation = ^BOOL(void) {
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:NEEDS_TO_REGISTER_PUSH_KEY];
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL success;
__block failedBlock failedBlock = ^(NSError *error) {
success = NO;
dispatch_semaphore_signal(sema);
};
[[PushManager sharedManager] requestPushTokenWithSuccess:^(NSString *pushToken, NSString *voipToken) {
[TSAccountManager registerForPushNotifications:pushToken
voipToken:voipToken
success:^{
success = YES;
dispatch_semaphore_signal(sema);
}
failure:failedBlock];
}
failure:failedBlock];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return success;
};
LIControllerRetryBlock retryBlock = [LockInteractionController defaultNetworkRetry];
[LockInteractionController performBlock:blockingOperation
completionBlock:^{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NEEDS_TO_REGISTER_PUSH_KEY];
DDLogWarn(@"Successfully migrated to 2.1");
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NEEDS_TO_REGISTER_PUSH_KEY];
DDLogWarn(@"Successfully migrated to 2.1");
}
retryBlock:retryBlock
usesNetwork:YES];
}
+ (BOOL)needsRegisterPush {
@ -137,15 +147,15 @@
}
+ (void)clearVideoCache {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
basePath = [basePath stringByAppendingPathComponent:@"videos"];
basePath = [basePath stringByAppendingPathComponent:@"videos"];
NSError *error;
if([[NSFileManager defaultManager] fileExistsAtPath:basePath]){
if ([[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
[NSFileManager.defaultManager removeItemAtPath:basePath error:&error];
}
if (error) {
DDLogError(@"An error occured while removing the videos cache folder from old location: %@",
error.debugDescription);
@ -159,46 +169,47 @@
}
+ (void)blockingAttributesUpdate {
LIControllerBlockingOperation blockingOperation = ^BOOL(void){
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:NEEDS_TO_REGISTER_ATTRIBUTES];
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL success;
TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithUpdatedAttributes];
[[TSNetworkManager sharedManager] queueAuthenticatedRequest:request success:^(NSURLSessionDataTask *task, id responseObject) {
LIControllerBlockingOperation blockingOperation = ^BOOL(void) {
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:NEEDS_TO_REGISTER_ATTRIBUTES];
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL success;
TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithUpdatedAttributesWithVoice:YES];
[[TSNetworkManager sharedManager] makeRequest:request
success:^(NSURLSessionDataTask *task, id responseObject) {
success = YES;
dispatch_semaphore_signal(sema);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
success = NO;
DDLogError(@"Updating attributess failed with error: %@", error.description);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return success;
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return success;
};
LIControllerRetryBlock retryBlock = [LockInteractionController defaultNetworkRetry];
[LockInteractionController performBlock:blockingOperation
completionBlock:^{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NEEDS_TO_REGISTER_ATTRIBUTES];
DDLogWarn(@"Successfully updated attributes.");
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NEEDS_TO_REGISTER_ATTRIBUTES];
DDLogWarn(@"Successfully updated attributes.");
}
retryBlock:retryBlock
usesNetwork:YES];
}
#pragma mark Util
+ (BOOL)userDefaultsBoolForKey:(NSString*)key {
+ (BOOL)userDefaultsBoolForKey:(NSString *)key {
NSNumber *num = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (!num) {
return NO;
} else {

View File

@ -100,21 +100,3 @@
#ifdef NSFoundationVersionNumber_iOS_9
#define _iOS_9 NSFoundationVersionNumber_iOS_9
#endif
/**
Add a Singelton implementation to the .m File
*/
#define MacrosSingletonImplemention \
+ (instancetype)sharedInstance { \
\
static dispatch_once_t onceToken; \
static id sharedInstance = nil; \
dispatch_once(&onceToken, ^{ \
sharedInstance = [self.class new]; \
}); \
\
return sharedInstance; \
}
#define MacrosSingletonInterface + (instancetype)sharedInstance;

Some files were not shown because too many files have changed in this diff Show More