Create SignalUtilitiesKit

pull/311/head
nielsandriesse 4 years ago
parent 82127bfe4d
commit c475f895e8

@ -98,6 +98,26 @@ target 'SignalMessaging' do
shared_pods
end
target 'SignalUtilitiesKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true
pod 'Curve25519Kit', :inhibit_warnings => true
pod 'GRKOpenSSLFramework', :inhibit_warnings => true
pod 'HKDFKit', :inhibit_warnings => true
pod 'libPhoneNumber-iOS', :inhibit_warnings => true
pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true
pod 'PromiseKit', :inhibit_warnings => true
pod 'Reachability', :inhibit_warnings => true
pod 'SAMKeychain', :inhibit_warnings => true
pod 'Starscream', git: 'https://github.com/signalapp/Starscream.git', branch: 'signal-release', :inhibit_warnings => true
pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true
pod 'YapDatabase/SQLCipher', :git => 'https://github.com/signalapp/YapDatabase.git', branch: 'signal-release', :inhibit_warnings => true
end
target 'SessionUIKit' do
end
target 'SessionMessagingKit' do
pod 'AFNetworking', inhibit_warnings: true
pod 'CryptoSwift', :inhibit_warnings => true

@ -206,12 +206,14 @@ DEPENDENCIES:
- FeedKit (~> 8.1)
- GRKOpenSSLFramework
- HKDFKit
- libPhoneNumber-iOS
- Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`)
- NVActivityIndicatorView (~> 4.7)
- PromiseKit
- PromiseKit (= 6.5.3)
- PureLayout (~> 3.1.4)
- Reachability
- SAMKeychain
- SessionAxolotlKit (from `https://github.com/loki-project/session-ios-protocol-kit.git`, branch `master`)
- SessionAxolotlKit/Tests (from `https://github.com/loki-project/session-ios-protocol-kit.git`, branch `master`)
- SessionCoreKit (from `https://github.com/loki-project/session-ios-core-kit.git`)
@ -333,6 +335,6 @@ SPEC CHECKSUMS:
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 8fc5917e97576b902a46b328af80664381ede889
PODFILE CHECKSUM: d78dc9a752cd3ce8f01fa327b8518dff3f5236d5
COCOAPODS: 1.10.0.rc.1

@ -1 +1 @@
Subproject commit 0c79ca436b633fdf1b0daf90e86fd323dcc60c55
Subproject commit e28da414f77b9cba508c92e90b16b815847cde7e

@ -13,7 +13,7 @@ public struct Configuration {
internal static var shared: Configuration!
}
public enum SessionMessagingKit { // Just to make the external API nice
public enum SessionMessagingKitX { // Just to make the external API nice
public static func configure(
storage: SessionMessagingKitStorageProtocol,

@ -2,15 +2,15 @@ import PromiseKit
import SessionSnodeKit
import SessionUtilitiesKit
internal enum MessageSender {
public enum MessageSender {
internal enum Error : LocalizedError {
public enum Error : LocalizedError {
case invalidMessage
case protoConversionFailed
case proofOfWorkCalculationFailed
case noUserPublicKey
internal var errorDescription: String? {
public var errorDescription: String? {
switch self {
case .invalidMessage: return "Invalid message."
case .protoConversionFailed: return "Couldn't convert message to proto."

@ -4,14 +4,20 @@ FOUNDATION_EXPORT double SessionProtocolKitVersionNumber;
FOUNDATION_EXPORT const unsigned char SessionProtocolKitVersionString[];
#import <SessionProtocolKit/AxolotlStore.h>
#import <SessionProtocolKit/AxolotlExceptions.h>
#import <SessionProtocolKit/ClosedGroupCiphertextMessage.h>
#import <SessionProtocolKit/Cryptography.h>
#import <SessionProtocolKit/FallbackMessage.h>
#import <SessionProtocolKit/NSData+keyVersionByte.h>
#import <SessionProtocolKit/NSData+messagePadding.h>
#import <SessionProtocolKit/NSData+OWS.h>
#import <SessionProtocolKit/NSDate+OWS.h>
#import <SessionProtocolKit/NSObject+OWS.h>
#import <SessionProtocolKit/NSString+OWS.h>
#import <SessionProtocolKit/OWSAsserts.h>
#import <SessionProtocolKit/OWSLogs.h>
#import <SessionProtocolKit/PreKeyBundle.h>
#import <SessionProtocolKit/SerializationUtilities.h>
#import <SessionProtocolKit/SessionBuilder.h>
#import <SessionProtocolKit/SessionCipher.h>
#import <SessionProtocolKit/Threading.h>

@ -5,7 +5,7 @@ public final class ClosedGroupSenderKey : NSObject, NSCoding { // NSObject/NSCod
public let publicKey: Data
// MARK: Initialization
init(chainKey: Data, keyIndex: UInt, publicKey: Data) {
public init(chainKey: Data, keyIndex: UInt, publicKey: Data) {
self.chainKey = chainKey
self.keyIndex = keyIndex
self.publicKey = publicKey

@ -7,7 +7,7 @@
#import "NSData+OWS.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
#import <SessionProtocolKit/Randomness.h>
#import <Curve25519Kit/Randomness.h>
#import <openssl/evp.h>
#import <SessionProtocolKit/OWSAsserts.h>

@ -1,20 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Randomness : NSObject
/**
* Generates a given number of cryptographically secure bytes using SecRandomCopyBytes.
*
* @param numberBytes The number of bytes to be generated.
*
* @return Random Bytes.
*/
+ (NSData *)generateRandomBytes:(int)numberBytes;
@end

@ -1,24 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "Randomness.h"
#import <SessionProtocolKit/OWSAsserts.h>
@implementation Randomness
+ (NSData *)generateRandomBytes:(int)numberBytes
{
NSMutableData *_Nullable randomBytes = [NSMutableData dataWithLength:numberBytes];
if (!randomBytes) {
OWSFail(@"Could not allocate buffer for random bytes.");
}
int err = 0;
err = SecRandomCopyBytes(kSecRandomDefault, numberBytes, [randomBytes mutableBytes]);
if (err != noErr || randomBytes.length != numberBytes) {
OWSFail(@"Could not generate random bytes.");
}
return [randomBytes copy];
}
@end

@ -3,7 +3,7 @@ import Foundation
public final class Snode : NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
public let address: String
public let port: UInt16
internal let publicKeySet: KeySet
public let publicKeySet: KeySet
public var ip: String {
address.removingPrefix("https://")
@ -16,9 +16,9 @@ public final class Snode : NSObject, NSCoding { // NSObject/NSCoding conformance
case sendMessage = "store"
}
internal struct KeySet {
let ed25519Key: String
let x25519Key: String
public struct KeySet {
public let ed25519Key: String
public let x25519Key: String
}
// MARK: Initialization

@ -1,15 +1,18 @@
import PromiseKit
import SessionUtilitiesKit
public enum SnodeAPI {
@objc(SNSnodeAPI)
public final class SnodeAPI : NSObject {
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
internal static var snodeFailureCount: [Snode:UInt] = [:]
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
internal static var snodePool: Set<Snode> = []
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
internal static var swarmCache: [String:Set<Snode>] = [:]
public static var swarmCache: [String:Set<Snode>] = [:]
public static var workQueue: DispatchQueue { Threading.workQueue }
// MARK: Settings
private static let maxRetryCount: UInt = 4
private static let minimumSnodePoolCount = 64
@ -25,11 +28,13 @@ public enum SnodeAPI {
// MARK: Error
public enum Error : LocalizedError {
case generic
case clockOutOfSync
case randomSnodePoolUpdatingFailed
public var errorDescription: String? {
switch self {
case .generic: return "An error occurred."
case .clockOutOfSync: return "Your clock is out of sync with the service node network."
case .randomSnodePoolUpdatingFailed: return "Failed to update random service node pool."
}
@ -41,8 +46,8 @@ public enum SnodeAPI {
public typealias RawResponse = Any
public typealias RawResponsePromise = Promise<RawResponse>
// MARK: Core
internal static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String, parameters: JSON) -> RawResponsePromise {
// MARK: Internal API
public static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String, parameters: JSON) -> RawResponsePromise {
if useOnionRequests {
return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any }
} else {
@ -109,35 +114,6 @@ public enum SnodeAPI {
}
}
internal static func getSwarm(for publicKey: String, isForcedReload: Bool = false) -> Promise<Set<Snode>> {
if swarmCache[publicKey] == nil {
swarmCache[publicKey] = Configuration.shared.storage.getSwarm(for: publicKey)
}
if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minimumSwarmSnodeCount && !isForcedReload {
return Promise<Set<Snode>> { $0.fulfill(cachedSwarm) }
} else {
SNLog("Getting swarm for: \((publicKey == Configuration.shared.storage.getUserPublicKey()) ? "self" : publicKey).")
let parameters: [String:Any] = [ "pubKey" : publicKey ]
return getRandomSnode().then2 { snode in
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters)
}
}.map2 { rawSnodes in
let swarm = parseSnodes(from: rawSnodes)
swarmCache[publicKey] = swarm
Configuration.shared.storage.with { transaction in
Configuration.shared.storage.setSwarm(to: swarm, for: publicKey, using: transaction)
}
return swarm
}
}
}
internal static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> {
// shuffled() uses the system's default random generator, which is cryptographically secure
return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) }
}
internal static func dropSnodeFromSnodePool(_ snode: Snode) {
var snodePool = SnodeAPI.snodePool
snodePool.remove(snode)
@ -154,7 +130,8 @@ public enum SnodeAPI {
}
}
internal static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) {
// MARK: Public API
public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) {
let swarm = SnodeAPI.swarmCache[publicKey]
if var swarm = swarm, let index = swarm.firstIndex(of: snode) {
swarm.remove(at: index)
@ -165,16 +142,55 @@ public enum SnodeAPI {
}
}
// MARK: Receiving
public static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> {
// shuffled() uses the system's default random generator, which is cryptographically secure
return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) }
}
public static func getSwarm(for publicKey: String, isForcedReload: Bool = false) -> Promise<Set<Snode>> {
if swarmCache[publicKey] == nil {
swarmCache[publicKey] = Configuration.shared.storage.getSwarm(for: publicKey)
}
if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minimumSwarmSnodeCount && !isForcedReload {
return Promise<Set<Snode>> { $0.fulfill(cachedSwarm) }
} else {
SNLog("Getting swarm for: \((publicKey == Configuration.shared.storage.getUserPublicKey()) ? "self" : publicKey).")
let parameters: [String:Any] = [ "pubKey" : publicKey ]
return getRandomSnode().then2 { snode in
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters)
}
}.map2 { rawSnodes in
let swarm = parseSnodes(from: rawSnodes)
swarmCache[publicKey] = swarm
Configuration.shared.storage.with { transaction in
Configuration.shared.storage.setSwarm(to: swarm, for: publicKey, using: transaction)
}
return swarm
}
}
}
public static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise {
let storage = Configuration.shared.storage
storage.with { transaction in
storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey, using: transaction)
}
let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? ""
let parameters = [ "pubKey" : publicKey, "lastHash" : lastHash ]
return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters)
}
public static func getMessages(for publicKey: String) -> Promise<Set<MessageListPromise>> {
let (promise, seal) = Promise<Set<MessageListPromise>>.pending()
let storage = Configuration.shared.storage
Threading.workQueue.async {
attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
getTargetSnodes(for: publicKey).mapValues2 { targetSnode in
Configuration.shared.storage.with { transaction in
Configuration.shared.storage.pruneLastMessageHashInfoIfExpired(for: targetSnode, associatedWith: publicKey, using: transaction)
storage.with { transaction in
storage.pruneLastMessageHashInfoIfExpired(for: targetSnode, associatedWith: publicKey, using: transaction)
}
let lastHash = Configuration.shared.storage.getLastMessageHash(for: targetSnode, associatedWith: publicKey) ?? ""
let lastHash = storage.getLastMessageHash(for: targetSnode, associatedWith: publicKey) ?? ""
let parameters = [ "pubKey" : publicKey, "lastHash" : lastHash ]
return invoke(.getMessages, on: targetSnode, associatedWith: publicKey, parameters: parameters).map2 { rawResponse in
parseRawMessagesResponse(rawResponse, from: targetSnode, associatedWith: publicKey)
@ -185,7 +201,6 @@ public enum SnodeAPI {
return promise
}
// MARK: Sending
public static func sendMessage(_ message: SnodeMessage) -> Promise<Set<RawResponsePromise>> {
let (promise, seal) = Promise<Set<RawResponsePromise>>.pending()
let publicKey = message.recipient
@ -230,7 +245,7 @@ public enum SnodeAPI {
})
}
internal static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [JSON] {
public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [JSON] {
guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] }
updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages)
return removeDuplicates(from: rawMessages, associatedWith: publicKey)

@ -1,6 +1,6 @@
import PromiseKit
internal extension Thenable {
public extension Thenable {
@discardableResult
func then2<U>(_ body: @escaping (T) throws -> U) -> Promise<U.T> where U : Thenable {
@ -23,7 +23,7 @@ internal extension Thenable {
}
}
internal extension Thenable where T: Sequence {
public extension Thenable where T: Sequence {
@discardableResult
func mapValues2<U>(_ transform: @escaping (T.Iterator.Element) throws -> U) -> Promise<[U]> {
@ -31,7 +31,7 @@ internal extension Thenable where T: Sequence {
}
}
internal extension Guarantee {
public extension Guarantee {
@discardableResult
func then2<U>(_ body: @escaping (T) -> Guarantee<U>) -> Guarantee<U> {
@ -54,7 +54,7 @@ internal extension Guarantee {
}
}
internal extension CatchMixin {
public extension CatchMixin {
@discardableResult
func catch2(_ body: @escaping (Error) -> Void) -> PMKFinalizer {
@ -77,7 +77,7 @@ internal extension CatchMixin {
}
}
internal extension CatchMixin where T == Void {
public extension CatchMixin where T == Void {
@discardableResult
func recover2(_ body: @escaping(Error) -> Void) -> Guarantee<Void> {

@ -1,7 +1,7 @@
import PromiseKit
/// Delay the execution of the promise constructed in `body` by `delay` seconds.
internal func withDelay<T>(_ delay: TimeInterval, completionQueue: DispatchQueue, body: @escaping () -> Promise<T>) -> Promise<T> {
public func withDelay<T>(_ delay: TimeInterval, completionQueue: DispatchQueue, body: @escaping () -> Promise<T>) -> Promise<T> {
#if DEBUG
assert(Thread.current.isMainThread) // Timers don't do well on background queues
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
// TODO define actual type, and validate length
public typealias IdentityKey = Data
/// based on libsignal-service-java's AccountManager class
@objc(SSKAccountServiceClient)
public class AccountServiceClient: NSObject {
static var shared = AccountServiceClient()
private let serviceClient: SignalServiceClient
override init() {
self.serviceClient = SignalServiceRestClient()
}
public func getPreKeysCount() -> Promise<Int> {
return serviceClient.getAvailablePreKeys()
}
public func setPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void> {
return serviceClient.registerPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords)
}
public func setSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise<Void> {
return serviceClient.setCurrentSignedPreKey(signedPreKey)
}
}

@ -0,0 +1,10 @@
import PromiseKit
public extension AnyPromise {
public static func from<T : Any>(_ promise: Promise<T>) -> AnyPromise {
let result = AnyPromise(promise)
result.retainUntilComplete()
return result
}
}

@ -0,0 +1,130 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
static inline BOOL OWSIsDebugBuild()
{
#ifdef DEBUG
return YES;
#else
return NO;
#endif
}
// These are fired whenever the corresponding "main app" or "app extension"
// notification is fired.
//
// 1. This saves you the work of observing both.
// 2. This allows us to ensure that any critical work (e.g. re-opening
// databases) has been done before app re-enters foreground, etc.
extern NSString *const OWSApplicationDidEnterBackgroundNotification;
extern NSString *const OWSApplicationWillEnterForegroundNotification;
extern NSString *const OWSApplicationWillResignActiveNotification;
extern NSString *const OWSApplicationDidBecomeActiveNotification;
typedef void (^BackgroundTaskExpirationHandler)(void);
typedef void (^AppActiveBlock)(void);
NSString *NSStringForUIApplicationState(UIApplicationState value);
@class OWSAES256Key;
@protocol SSKKeychainStorage;
@protocol AppContext <NSObject>
@property (nonatomic, readonly) BOOL isMainApp;
@property (nonatomic, readonly) BOOL isMainAppAndActive;
/// Whether the app was woken up by a silent push notification. This is important for
/// determining whether attachments should be downloaded or not.
@property (nonatomic) BOOL wasWokenUpByPushNotification;
// Whether the user is using a right-to-left language like Arabic.
@property (nonatomic, readonly) BOOL isRTL;
@property (nonatomic, readonly) BOOL isRunningTests;
@property (atomic, nullable) UIWindow *mainWindow;
// Unlike UIApplication.applicationState, this is thread-safe.
// It contains the "last known" application state.
//
// Because it is updated in response to "will/did-style" events, it is
// conservative and skews toward less-active and not-foreground:
//
// * It doesn't report "is active" until the app is active
// and reports "inactive" as soon as it _will become_ inactive.
// * It doesn't report "is foreground (but inactive)" until the app is
// foreground & inactive and reports "background" as soon as it _will
// enter_ background.
//
// This conservatism is useful, since we want to err on the side of
// caution when, for example, we do work that should only be done
// when the app is foreground and active.
@property (atomic, readonly) UIApplicationState reportedApplicationState;
// A convenience accessor for reportedApplicationState.
//
// This method is thread-safe.
- (BOOL)isInBackground;
// A convenience accessor for reportedApplicationState.
//
// This method is thread-safe.
- (BOOL)isAppForegroundAndActive;
// Should start a background task if isMainApp is YES.
// Should just return UIBackgroundTaskInvalid if isMainApp is NO.
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:
(BackgroundTaskExpirationHandler)expirationHandler;
// Should be a NOOP if isMainApp is NO.
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier;
// Should be a NOOP if isMainApp is NO.
- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray<id> *)blockingObjects;
// Should only be called if isMainApp is YES.
- (void)setMainAppBadgeNumber:(NSInteger)value;
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated;
@property (nonatomic, readonly) CGFloat statusBarHeight;
// Returns the VC that should be used to present alerts, modals, etc.
- (nullable UIViewController *)frontmostViewController;
// Returns nil if isMainApp is NO
@property (nullable, nonatomic, readonly) UIAlertAction *openSystemSettingsAction;
// Should be a NOOP if isMainApp is NO.
- (void)setNetworkActivityIndicatorVisible:(BOOL)value;
- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block;
@property (atomic, readonly) NSDate *appLaunchTime;
- (id<SSKKeychainStorage>)keychainStorage;
- (NSString *)appDocumentDirectoryPath;
- (NSString *)appSharedDataDirectoryPath;
- (NSUserDefaults *)appUserDefaults;
@end
id<AppContext> CurrentAppContext(void);
void SetCurrentAppContext(id<AppContext> appContext);
void ExitShareExtension(void);
#ifdef DEBUG
void ClearCurrentAppContextForTests(void);
#endif
NS_ASSUME_NONNULL_END

@ -0,0 +1,61 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "AppContext.h"
#import <SessionProtocolKit/SessionProtocolKit.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSApplicationDidEnterBackgroundNotification = @"OWSApplicationDidEnterBackgroundNotification";
NSString *const OWSApplicationWillEnterForegroundNotification = @"OWSApplicationWillEnterForegroundNotification";
NSString *const OWSApplicationWillResignActiveNotification = @"OWSApplicationWillResignActiveNotification";
NSString *const OWSApplicationDidBecomeActiveNotification = @"OWSApplicationDidBecomeActiveNotification";
NSString *NSStringForUIApplicationState(UIApplicationState value)
{
switch (value) {
case UIApplicationStateActive:
return @"UIApplicationStateActive";
case UIApplicationStateInactive:
return @"UIApplicationStateInactive";
case UIApplicationStateBackground:
return @"UIApplicationStateBackground";
}
}
static id<AppContext> currentAppContext = nil;
id<AppContext> CurrentAppContext(void)
{
OWSCAssertDebug(currentAppContext);
return currentAppContext;
}
void SetCurrentAppContext(id<AppContext> appContext)
{
// The main app context should only be set once.
//
// App extensions may be opened multiple times in the same process,
// so statics will persist.
OWSCAssertDebug(!currentAppContext || !currentAppContext.isMainApp);
currentAppContext = appContext;
}
#ifdef DEBUG
void ClearCurrentAppContextForTests()
{
currentAppContext = nil;
}
#endif
void ExitShareExtension(void)
{
OWSLogInfo(@"ExitShareExtension");
[DDLog flushLog];
exit(0);
}
NS_ASSUME_NONNULL_END

@ -0,0 +1,38 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^AppReadyBlock)(void);
@interface AppReadiness : NSObject
- (instancetype)init NS_UNAVAILABLE;
// This method can be called on any thread.
+ (BOOL)isAppReady;
// This method should only be called on the main thread.
+ (void)setAppIsReady;
// If the app is ready, the block is called immediately;
// otherwise it is called when the app becomes ready.
//
// This method should only be called on the main thread.
// The block will always be called on the main thread.
//
// * The "will become ready" blocks are called before the "did become ready" blocks.
// * The "will become ready" blocks should be used for internal setup of components
// so that they are ready to interact with other components of the system.
// * The "did become ready" blocks should be used for any work that should be done
// on app launch, especially work that uses other components.
// * We should usually use "did become ready" blocks since they are safer.
+ (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block NS_SWIFT_NAME(runNowOrWhenAppWillBecomeReady(_:));
+ (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block NS_SWIFT_NAME(runNowOrWhenAppDidBecomeReady(_:));
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,144 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "AppReadiness.h"
#import <SessionProtocolKit/SessionProtocolKit.h>
#import "AppContext.h"
#import "SSKAsserts.h"
NS_ASSUME_NONNULL_BEGIN
@interface AppReadiness ()
@property (atomic) BOOL isAppReady;
@property (nonatomic) NSMutableArray<AppReadyBlock> *appWillBecomeReadyBlocks;
@property (nonatomic) NSMutableArray<AppReadyBlock> *appDidBecomeReadyBlocks;
@end
#pragma mark -
@implementation AppReadiness
+ (instancetype)sharedManager
{
static AppReadiness *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initDefault];
});
return sharedMyManager;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSSingletonAssert();
self.appWillBecomeReadyBlocks = [NSMutableArray new];
self.appDidBecomeReadyBlocks = [NSMutableArray new];
return self;
}
+ (BOOL)isAppReady
{
return [self.sharedManager isAppReady];
}
+ (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block
{
DispatchMainThreadSafe(^{
[self.sharedManager runNowOrWhenAppWillBecomeReady:block];
});
}
- (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block
{
OWSAssertIsOnMainThread();
OWSAssertDebug(block);
if (CurrentAppContext().isRunningTests) {
// We don't need to do any "on app ready" work in the tests.
return;
}
if (self.isAppReady) {
block();
return;
}
[self.appWillBecomeReadyBlocks addObject:block];
}
+ (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block
{
DispatchMainThreadSafe(^{
[self.sharedManager runNowOrWhenAppDidBecomeReady:block];
});
}
- (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block
{
OWSAssertIsOnMainThread();
OWSAssertDebug(block);
if (CurrentAppContext().isRunningTests) {
// We don't need to do any "on app ready" work in the tests.
return;
}
if (self.isAppReady) {
block();
return;
}
[self.appDidBecomeReadyBlocks addObject:block];
}
+ (void)setAppIsReady
{
[self.sharedManager setAppIsReady];
}
- (void)setAppIsReady
{
OWSAssertIsOnMainThread();
OWSAssertDebug(!self.isAppReady);
OWSLogInfo(@"");
self.isAppReady = YES;
[self runAppReadyBlocks];
}
- (void)runAppReadyBlocks
{
OWSAssertIsOnMainThread();
OWSAssertDebug(self.isAppReady);
NSArray<AppReadyBlock> *appWillBecomeReadyBlocks = [self.appWillBecomeReadyBlocks copy];
[self.appWillBecomeReadyBlocks removeAllObjects];
NSArray<AppReadyBlock> *appDidBecomeReadyBlocks = [self.appDidBecomeReadyBlocks copy];
[self.appDidBecomeReadyBlocks removeAllObjects];
// We invoke the _will become_ blocks before the _did become_ blocks.
for (AppReadyBlock block in appWillBecomeReadyBlocks) {
block();
}
for (AppReadyBlock block in appDidBecomeReadyBlocks) {
block();
}
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,32 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AppVersion : NSObject
// The properties are updated immediately after launch.
@property (atomic, readonly) NSString *firstAppVersion;
@property (atomic, nullable, readonly) NSString *lastAppVersion;
@property (atomic, readonly) NSString *currentAppVersion;
// There properties aren't updated until appLaunchDidComplete is called.
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchAppVersion;
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchMainAppVersion;
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchSAEAppVersion;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedInstance;
- (void)mainAppLaunchDidComplete;
- (void)saeLaunchDidComplete;
- (BOOL)isFirstLaunch;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,133 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AppVersion.h"
#import "NSUserDefaults+OWS.h"
#import <SessionProtocolKit/SessionProtocolKit.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSUserDefaults_FirstAppVersion = @"kNSUserDefaults_FirstAppVersion";
NSString *const kNSUserDefaults_LastAppVersion = @"kNSUserDefaults_LastVersion";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion = @"kNSUserDefaults_LastCompletedLaunchAppVersion";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp
= @"kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_SAE
= @"kNSUserDefaults_LastCompletedLaunchAppVersion_SAE";
@interface AppVersion ()
@property (atomic) NSString *firstAppVersion;
@property (atomic, nullable) NSString *lastAppVersion;
@property (atomic) NSString *currentAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchMainAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchSAEAppVersion;
@end
#pragma mark -
@implementation AppVersion
+ (instancetype)sharedInstance
{
static AppVersion *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [AppVersion new];
[instance configure];
});
return instance;
}
- (void)configure {
OWSAssertIsOnMainThread();
self.currentAppVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
// The version of the app when it was first launched.
// nil if the app has never been launched before.
self.firstAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_FirstAppVersion];
// The version of the app the last time it was launched.
// nil if the app has never been launched before.
self.lastAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastAppVersion];
self.lastCompletedLaunchAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion];
self.lastCompletedLaunchMainAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp];
self.lastCompletedLaunchSAEAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE];
// Ensure the value for the "first launched version".
if (!self.firstAppVersion) {
self.firstAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_FirstAppVersion];
}
// Update the value for the "most recently launched version".
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_LastAppVersion];
[[NSUserDefaults appUserDefaults] synchronize];
// The long version string looks like an IPv4 address.
// To prevent the log scrubber from scrubbing it,
// we replace . with _.
NSString *longVersionString = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
stringByReplacingOccurrencesOfString:@"."
withString:@"_"];
OWSLogInfo(@"firstAppVersion: %@", self.firstAppVersion);
OWSLogInfo(@"lastAppVersion: %@", self.lastAppVersion);
OWSLogInfo(@"currentAppVersion: %@ (%@)", self.currentAppVersion, longVersionString);
OWSLogInfo(@"lastCompletedLaunchAppVersion: %@", self.lastCompletedLaunchAppVersion);
OWSLogInfo(@"lastCompletedLaunchMainAppVersion: %@", self.lastCompletedLaunchMainAppVersion);
OWSLogInfo(@"lastCompletedLaunchSAEAppVersion: %@", self.lastCompletedLaunchSAEAppVersion);
}
- (void)appLaunchDidComplete
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"appLaunchDidComplete");
self.lastCompletedLaunchAppVersion = self.currentAppVersion;
// Update the value for the "most recently launch-completed version".
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion];
[[NSUserDefaults appUserDefaults] synchronize];
}
- (void)mainAppLaunchDidComplete
{
OWSAssertIsOnMainThread();
self.lastCompletedLaunchMainAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp];
[self appLaunchDidComplete];
}
- (void)saeLaunchDidComplete
{
OWSAssertIsOnMainThread();
self.lastCompletedLaunchSAEAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE];
[self appLaunchDidComplete];
}
- (BOOL)isFirstLaunch
{
return self.firstAppVersion != nil;
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,7 @@
public extension Array where Element : CustomStringConvertible {
public var prettifiedDescription: String {
return "[ " + map { $0.description }.joined(separator: ", ") + " ]"
}
}

@ -0,0 +1,21 @@
public enum BuildConfiguration : String, CustomStringConvertible {
case debug, production
public static let current: BuildConfiguration = {
#if DEBUG
return .debug
#else
return .production
#endif
}()
public var description: String { return rawValue }
}
@objc public final class LKBuildConfiguration : NSObject {
override private init() { }
@objc public static var current: String { return BuildConfiguration.current.description }
}

@ -0,0 +1,40 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ByteParser : NSObject
@property (nonatomic, readonly) BOOL hasError;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian;
#pragma mark - Short
- (uint16_t)shortAtIndex:(NSUInteger)index;
- (uint16_t)nextShort;
#pragma mark - Int
- (uint32_t)intAtIndex:(NSUInteger)index;
- (uint32_t)nextInt;
#pragma mark - Long
- (uint64_t)longAtIndex:(NSUInteger)index;
- (uint64_t)nextLong;
#pragma mark -
- (BOOL)readZero:(NSUInteger)length;
- (nullable NSData *)readBytes:(NSUInteger)length;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,143 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "ByteParser.h"
#import <SessionProtocolKit/SessionProtocolKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ByteParser ()
@property (nonatomic, readonly) BOOL littleEndian;
@property (nonatomic, readonly) NSData *data;
@property (nonatomic) NSUInteger cursor;
@property (nonatomic) BOOL hasError;
@end
#pragma mark -
@implementation ByteParser
- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian
{
if (self = [super init]) {
_littleEndian = littleEndian;
_data = data;
}
return self;
}
#pragma mark - Short
- (uint16_t)shortAtIndex:(NSUInteger)index
{
uint16_t value;
const size_t valueSize = sizeof(value);
OWSAssertDebug(valueSize == 2);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt16LittleToHost(value);
} else {
return CFSwapInt16BigToHost(value);
}
}
- (uint16_t)nextShort
{
uint16_t value = [self shortAtIndex:self.cursor];
self.cursor += sizeof(value);
return value;
}
#pragma mark - Int
- (uint32_t)intAtIndex:(NSUInteger)index
{
uint32_t value;
const size_t valueSize = sizeof(value);
OWSAssertDebug(valueSize == 4);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt32LittleToHost(value);
} else {
return CFSwapInt32BigToHost(value);
}
}
- (uint32_t)nextInt
{
uint32_t value = [self intAtIndex:self.cursor];
self.cursor += sizeof(value);
return value;
}
#pragma mark - Long
- (uint64_t)longAtIndex:(NSUInteger)index
{
uint64_t value;
const size_t valueSize = sizeof(value);
OWSAssertDebug(valueSize == 8);
if (index + valueSize > self.data.length) {
self.hasError = YES;
return 0;
}
[self.data getBytes:&value range:NSMakeRange(index, valueSize)];
if (self.littleEndian) {
return CFSwapInt64LittleToHost(value);
} else {
return CFSwapInt64BigToHost(value);
}
}
- (uint64_t)nextLong
{
uint64_t value = [self longAtIndex:self.cursor];
self.cursor += sizeof(value);