parent
7c28805442
commit
a5f067936c
|
@ -125,7 +125,7 @@ EXTERNAL SOURCES:
|
|||
:branch: signal-master
|
||||
:git: https://github.com/WhisperSystems/JSQMessagesViewController.git
|
||||
SignalServiceKit:
|
||||
:path: .
|
||||
:path: "."
|
||||
SocketRocket:
|
||||
:git: https://github.com/facebook/SocketRocket.git
|
||||
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
4542F0961EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */; };
|
||||
4542F0971EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */; };
|
||||
45464DBC1DFA041F001D3FD6 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; };
|
||||
4563ADF11F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */; };
|
||||
45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */; };
|
||||
45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666EC81D994C0D008FE134 /* OWSGroupAvatarBuilder.m */; };
|
||||
45666F561D9B2827008FE134 /* OWSScrubbingLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F551D9B2827008FE134 /* OWSScrubbingLogFormatter.m */; };
|
||||
|
@ -566,6 +567,7 @@
|
|||
4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+retainUntilComplete.swift"; sourceTree = "<group>"; };
|
||||
45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataChannelMessage.swift; sourceTree = "<group>"; };
|
||||
454B35071D08EED80026D658 /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = translations/mk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWS106EnsureProfileComplete.swift; sourceTree = "<group>"; };
|
||||
45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAvatarBuilder.h; sourceTree = "<group>"; };
|
||||
45666EC51D99483D008FE134 /* OWSAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAvatarBuilder.m; sourceTree = "<group>"; };
|
||||
45666EC71D994C0D008FE134 /* OWSGroupAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSGroupAvatarBuilder.h; sourceTree = "<group>"; };
|
||||
|
@ -1130,6 +1132,7 @@
|
|||
45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */,
|
||||
45666F7C1D9C0814008FE134 /* OWSDatabaseMigrationRunner.h */,
|
||||
45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */,
|
||||
4563ADF01F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift */,
|
||||
);
|
||||
name = Migrations;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2144,6 +2147,7 @@
|
|||
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */,
|
||||
76EB058A18170B33006006FC /* Release.m in Sources */,
|
||||
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
|
||||
4563ADF11F22BD7100DEB8C7 /* OWS106EnsureProfileComplete.swift in Sources */,
|
||||
450873C71D9D867B006B54F2 /* OWSIncomingMessageCollectionViewCell.m in Sources */,
|
||||
76EB057A18170B33006006FC /* OWSContactsManager.m in Sources */,
|
||||
76EB064218170B33006006FC /* StringUtil.m in Sources */,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
@objc
|
||||
class ProfileFetcherJob: NSObject {
|
||||
|
@ -12,34 +13,54 @@ class ProfileFetcherJob: NSObject {
|
|||
let networkManager: TSNetworkManager
|
||||
let storageManager: TSStorageManager
|
||||
|
||||
let thread: TSThread
|
||||
|
||||
// This property is only accessed on the main queue.
|
||||
static var fetchDateMap = [String: Date]()
|
||||
|
||||
public class func run(thread: TSThread, networkManager: TSNetworkManager) {
|
||||
ProfileFetcherJob(thread: thread, networkManager: networkManager).run()
|
||||
ProfileFetcherJob(networkManager: networkManager).run(thread: thread)
|
||||
}
|
||||
|
||||
init(thread: TSThread, networkManager: TSNetworkManager) {
|
||||
init(networkManager: TSNetworkManager) {
|
||||
self.networkManager = networkManager
|
||||
self.storageManager = TSStorageManager.shared()
|
||||
|
||||
self.thread = thread
|
||||
}
|
||||
|
||||
public func run() {
|
||||
public func run(thread: TSThread) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for recipientId in self.thread.recipientIdentifiers {
|
||||
self.getProfile(recipientId: recipientId)
|
||||
for recipientId in thread.recipientIdentifiers {
|
||||
self.updateProfile(recipientId: recipientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func getProfile(recipientId: String, remainingRetries: Int = 3) {
|
||||
enum ProfileFetcherJobError: Error {
|
||||
case throttled(lastTimeInterval: TimeInterval),
|
||||
unknownNetworkError
|
||||
}
|
||||
|
||||
public func updateProfile(recipientId: String, remainingRetries: Int = 3) {
|
||||
self.getProfile(recipientId: recipientId).then { profile in
|
||||
self.updateProfile(signalServiceProfile: profile)
|
||||
}.catch { error in
|
||||
switch error {
|
||||
case ProfileFetcherJobError.throttled(let lastTimeInterval):
|
||||
Logger.info("\(self.TAG) skipping updateProfile: \(recipientId), lastTimeInterval: \(lastTimeInterval)")
|
||||
case let error as SignalServiceProfile.ValidationError:
|
||||
Logger.warn("\(self.TAG) skipping updateProfile retry. Invalid profile for: \(recipientId) error: \(error)")
|
||||
default:
|
||||
if remainingRetries > 0 {
|
||||
self.updateProfile(recipientId: recipientId, remainingRetries: remainingRetries - 1)
|
||||
} else {
|
||||
owsFail("\(self.TAG) in \(#function) failed to get profile with error: \(error)")
|
||||
}
|
||||
}
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
public func getProfile(recipientId: String) -> Promise<SignalServiceProfile> {
|
||||
AssertIsOnMainThread()
|
||||
if let lastDate = ProfileFetcherJob.fetchDateMap[recipientId] {
|
||||
let lastTimeInterval = fabs(lastDate.timeIntervalSinceNow)
|
||||
// Don't check a profile more often than every N minutes.
|
||||
|
@ -48,8 +69,7 @@ class ProfileFetcherJob: NSObject {
|
|||
// facilitate debugging.
|
||||
let kGetProfileMaxFrequencySeconds = _isDebugAssertConfiguration() ? 0 : 60.0 * 5.0
|
||||
guard lastTimeInterval > kGetProfileMaxFrequencySeconds else {
|
||||
Logger.info("\(self.TAG) skipping getProfile: \(recipientId), lastTimeInterval: \(lastTimeInterval)")
|
||||
return
|
||||
return Promise(error: ProfileFetcherJobError.throttled(lastTimeInterval: lastTimeInterval))
|
||||
}
|
||||
}
|
||||
ProfileFetcherJob.fetchDateMap[recipientId] = Date()
|
||||
|
@ -58,33 +78,31 @@ class ProfileFetcherJob: NSObject {
|
|||
|
||||
let request = OWSGetProfileRequest(recipientId: recipientId)
|
||||
|
||||
let (promise, fulfill, reject) = Promise<SignalServiceProfile>.pending()
|
||||
|
||||
self.networkManager.makeRequest(
|
||||
request,
|
||||
success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in
|
||||
guard let profileResponse = SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject) else {
|
||||
owsFail("\(self.TAG) response object had unexpected content")
|
||||
return
|
||||
do {
|
||||
let profile = try SignalServiceProfile(recipientId: recipientId, rawResponse: responseObject)
|
||||
fulfill(profile)
|
||||
} catch {
|
||||
reject(error)
|
||||
}
|
||||
|
||||
self.processResponse(signalServiceProfile: profileResponse)
|
||||
},
|
||||
failure: { (_: URLSessionDataTask?, error: Error?) in
|
||||
guard let error = error else {
|
||||
owsFail("\(self.TAG) error in \(#function) was surpringly nil. sheesh rough day.")
|
||||
return
|
||||
|
||||
if let error = error {
|
||||
reject(error)
|
||||
}
|
||||
|
||||
Logger.error("\(self.TAG) failed to fetch profile for recipient: \(recipientId) with error: \(error)")
|
||||
|
||||
if remainingRetries > 1 {
|
||||
DispatchQueue.global().async {
|
||||
self.getProfile(recipientId: recipientId, remainingRetries:remainingRetries - 1)
|
||||
}
|
||||
}
|
||||
reject(ProfileFetcherJobError.unknownNetworkError)
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
private func processResponse(signalServiceProfile: SignalServiceProfile) {
|
||||
private func updateProfile(signalServiceProfile: SignalServiceProfile) {
|
||||
verifyIdentityUpToDateAsync(recipientId: signalServiceProfile.recipientId, latestIdentityKey: signalServiceProfile.identityKey)
|
||||
|
||||
// Eventually we'll want to do more things with new SignalServiceProfile fields here.
|
||||
|
@ -105,31 +123,32 @@ class ProfileFetcherJob: NSObject {
|
|||
struct SignalServiceProfile {
|
||||
let TAG = "[SignalServiceProfile]"
|
||||
|
||||
enum ValidationError: Error {
|
||||
case invalid(description: String)
|
||||
case invalidIdentityKey(description: String)
|
||||
}
|
||||
|
||||
public let recipientId: String
|
||||
public let identityKey: Data
|
||||
|
||||
init?(recipientId: String, rawResponse: Any?) {
|
||||
init(recipientId: String, rawResponse: Any?) throws {
|
||||
self.recipientId = recipientId
|
||||
|
||||
guard let responseDict = rawResponse as? [String: Any?] else {
|
||||
Logger.error("\(TAG) unexpected type: \(String(describing: rawResponse))")
|
||||
return nil
|
||||
throw ValidationError.invalid(description: "\(TAG) unexpected type: \(String(describing: rawResponse))")
|
||||
}
|
||||
|
||||
guard let identityKeyString = responseDict["identityKey"] as? String else {
|
||||
Logger.error("\(TAG) missing identity key: \(String(describing: rawResponse))")
|
||||
return nil
|
||||
throw ValidationError.invalidIdentityKey(description: "\(TAG) missing identity key: \(String(describing: rawResponse))")
|
||||
}
|
||||
|
||||
guard let identityKeyWithType = Data(base64Encoded: identityKeyString) else {
|
||||
Logger.error("\(TAG) unable to parse identity key: \(identityKeyString)")
|
||||
return nil
|
||||
throw ValidationError.invalidIdentityKey(description: "\(TAG) unable to parse identity key: \(identityKeyString)")
|
||||
}
|
||||
|
||||
let kIdentityKeyLength = 33
|
||||
guard identityKeyWithType.count == kIdentityKeyLength else {
|
||||
Logger.error("\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)")
|
||||
return nil
|
||||
throw ValidationError.invalidIdentityKey(description: "\(TAG) malformed key \(identityKeyString) with decoded length: \(identityKeyWithType.count)")
|
||||
}
|
||||
|
||||
// `removeKeyType` is an objc category method only on NSData, so temporarily cast.
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "TSMessageAdapter.h"
|
||||
#import "AttachmentSharing.h"
|
||||
#import "Environment.h"
|
||||
#import "FLAnimatedImage.h"
|
||||
|
@ -15,6 +14,7 @@
|
|||
#import "OWSCallNotificationsAdaptee.h"
|
||||
#import "OWSContactAvatarBuilder.h"
|
||||
#import "OWSContactsManager.h"
|
||||
#import "OWSDatabaseMigration.h"
|
||||
#import "OWSLogger.h"
|
||||
#import "OWSMessageEditing.h"
|
||||
#import "OWSProgressView.h"
|
||||
|
@ -24,6 +24,7 @@
|
|||
#import "PushManager.h"
|
||||
#import "SettingsTableViewController.h"
|
||||
#import "SignalsViewController.h"
|
||||
#import "TSMessageAdapter.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "UIFont+OWS.h"
|
||||
#import "UIUtil.h"
|
||||
|
@ -77,6 +78,7 @@
|
|||
#import <SignalServiceKit/TSInfoMessage.h>
|
||||
#import <SignalServiceKit/TSMessagesManager.h>
|
||||
#import <SignalServiceKit/TSNetworkManager.h>
|
||||
#import <SignalServiceKit/TSPreKeyManager.h>
|
||||
#import <SignalServiceKit/TSSocketManager.h>
|
||||
#import <SignalServiceKit/TSStorageManager+Calling.h>
|
||||
#import <SignalServiceKit/TSStorageManager+SessionStore.h>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#import "OWS103EnableVideoCalling.h"
|
||||
#import "OWS104CreateRecipientIdentities.h"
|
||||
#import "OWS105AttachmentFilePaths.h"
|
||||
#import "Signal-Swift.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -32,7 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[[OWS102MoveLoggingPreferenceToUserDefaults alloc] initWithStorageManager:self.storageManager],
|
||||
[[OWS103EnableVideoCalling alloc] initWithStorageManager:self.storageManager],
|
||||
// OWS104CreateRecipientIdentities is run separately. See runSafeBlockingMigrations.
|
||||
[[OWS105AttachmentFilePaths alloc] initWithStorageManager:self.storageManager]
|
||||
[[OWS105AttachmentFilePaths alloc] initWithStorageManager:self.storageManager],
|
||||
[[OWS106EnsureProfileComplete alloc] initWithStorageManager:self.storageManager]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
class OWS106EnsureProfileComplete: OWSDatabaseMigration {
|
||||
|
||||
let TAG = "[OWS106EnsureProfileComplete]"
|
||||
|
||||
// increment a similar constant for each migration.
|
||||
class func migrationId() -> String {
|
||||
return "106"
|
||||
}
|
||||
|
||||
// Overriding runUp since we have some specific completion criteria which
|
||||
// is more likely to fail since it involves network requests.
|
||||
override func runUp() {
|
||||
CompleteRegistrationFixerJob(completionHandler: {
|
||||
Logger.info("\(self.TAG) Completed. Saving.")
|
||||
self.save()
|
||||
}).start()
|
||||
}
|
||||
|
||||
/**
|
||||
* A previous client bug made it possible for re-registering users to register their new account
|
||||
* but never upload new pre-keys. The symptom is that there will be accounts with no uploaded
|
||||
* identity key. We detect that here and fix the situation
|
||||
*/
|
||||
private class CompleteRegistrationFixerJob {
|
||||
|
||||
let TAG = "[CompleteRegistrationFixerJob]"
|
||||
|
||||
// Duration between retries if update fails.
|
||||
static let kRetryInterval: TimeInterval = 5 * 60
|
||||
|
||||
var timer: Timer?
|
||||
let completionHandler: () -> Void
|
||||
|
||||
init (completionHandler: @escaping () -> Void) {
|
||||
self.completionHandler = completionHandler
|
||||
}
|
||||
|
||||
func start() {
|
||||
assert(self.timer == nil)
|
||||
|
||||
let timer = WeakTimer.scheduledTimer(timeInterval: CompleteRegistrationFixerJob.kRetryInterval, target: self, userInfo: nil, repeats: true) { [weak self] timer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var isCompleted = false
|
||||
strongSelf.ensureProfileComplete().then { _ -> Void in
|
||||
guard isCompleted == false else {
|
||||
Logger.info("Already saved. Skipping redundant call.")
|
||||
return
|
||||
}
|
||||
Logger.info("\(strongSelf.TAG) complete. Canceling timer and saving.")
|
||||
isCompleted = true
|
||||
timer.invalidate()
|
||||
strongSelf.completionHandler()
|
||||
}.catch { error in
|
||||
Logger.error("\(strongSelf.TAG) failed with \(error). We'll try again in \(CompleteRegistrationFixerJob.kRetryInterval) seconds.")
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
self.timer = timer
|
||||
|
||||
timer.fire()
|
||||
}
|
||||
|
||||
func ensureProfileComplete() -> Promise<Void> {
|
||||
guard let localRecipientId = TSAccountManager.localNumber() else {
|
||||
// local app doesn't think we're registered, so nothing to worry about.
|
||||
return Promise(value: ())
|
||||
}
|
||||
|
||||
let (promise, fulfill, reject) = Promise<Void>.pending()
|
||||
|
||||
guard let networkManager = Environment.getCurrent().networkManager else {
|
||||
owsFail("\(TAG) network manager was unexpectedly not set")
|
||||
return Promise(error: OWSErrorMakeAssertionError())
|
||||
}
|
||||
|
||||
ProfileFetcherJob(networkManager: networkManager).getProfile(recipientId: localRecipientId).then { _ -> Void in
|
||||
Logger.info("\(self.TAG) verified recipient profile is in good shape: \(localRecipientId)")
|
||||
|
||||
fulfill()
|
||||
}.catch { error in
|
||||
switch error {
|
||||
case SignalServiceProfile.ValidationError.invalidIdentityKey(let description):
|
||||
Logger.warn("\(self.TAG) detected incomplete profile for \(localRecipientId) error: \(description)")
|
||||
// This is the error condition we're looking for. Update prekeys to properly set the identity key, completing registration.
|
||||
TSPreKeyManager.registerPreKeys(with: .signedAndOneTime,
|
||||
success: {
|
||||
Logger.info("\(self.TAG) successfully uploaded pre-keys. Profile should be fixed.")
|
||||
fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
reject(OWSErrorWithCodeDescription(.signalServiceFailure, "\(self.TAG) Unknown error in \(#function)"))
|
||||
})
|
||||
default:
|
||||
reject(error)
|
||||
}
|
||||
}.retainUntilComplete()
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ final class WeakTimer {
|
|||
action: action).timer!
|
||||
}
|
||||
|
||||
@objc fileprivate func fire(timer: Timer) {
|
||||
@objc public func fire(timer: Timer) {
|
||||
if target != nil {
|
||||
action(timer)
|
||||
} else {
|
||||
|
|
|
@ -78,6 +78,15 @@
|
|||
|
||||
#pragma mark Class Methods
|
||||
|
||||
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
|
||||
{
|
||||
if ([propertyKey isEqualToString:@"TAG"]) {
|
||||
return MTLPropertyStorageNone;
|
||||
} else {
|
||||
return [super storageBehaviorForPropertyWithKey:propertyKey];
|
||||
}
|
||||
}
|
||||
|
||||
+ (YapDatabaseConnection *)dbReadConnection
|
||||
{
|
||||
// We use TSYapDatabaseObject's dbReadWriteConnection (not TSStorageManager's
|
||||
|
|
Loading…
Reference in New Issue