Merge branch 'charlesmchen/outageDetection'

pull/1/head
Matthew Chen 7 years ago
commit e2de8f6ffa

@ -1 +1 @@
Subproject commit 5caa906cfd9c24f464247747004cc8335b96040c
Subproject commit d82a13db8aa161a7db21a442ad38b98bd458b951

@ -96,7 +96,6 @@
346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129591FD1D74B00532771 /* SignalKeyingStorage.m */; };
346129731FD1E01700532771 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451DE9F11DC1585F00810E42 /* PromiseKit.framework */; };
346129741FD1E02D00532771 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451DE9F11DC1585F00810E42 /* PromiseKit.framework */; };
346129761FD1E0B500532771 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129751FD1E0B500532771 /* WeakTimer.swift */; };
346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129931FD1E30000532771 /* OWSDatabaseMigration.h */; settings = {ATTRIBUTES = (Public, ); }; };
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129941FD1E30000532771 /* OWSDatabaseMigration.m */; };
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; };
@ -697,7 +696,6 @@
346129571FD1D74B00532771 /* Release.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Release.m; sourceTree = "<group>"; };
346129581FD1D74B00532771 /* SignalKeyingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalKeyingStorage.h; sourceTree = "<group>"; };
346129591FD1D74B00532771 /* SignalKeyingStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalKeyingStorage.m; sourceTree = "<group>"; };
346129751FD1E0B500532771 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
346129931FD1E30000532771 /* OWSDatabaseMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseMigration.h; sourceTree = "<group>"; };
346129941FD1E30000532771 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigration.m; sourceTree = "<group>"; };
346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = "<group>"; };
@ -1461,7 +1459,6 @@
B97940251832BD2400BD66CB /* UIUtil.h */,
B97940261832BD2400BD66CB /* UIUtil.m */,
45F170D51E315310003FC1F2 /* Weak.swift */,
346129751FD1E0B500532771 /* WeakTimer.swift */,
);
path = utils;
sourceTree = "<group>";
@ -3076,7 +3073,6 @@
344F249A200FD03300CFB4F4 /* MessageApprovalViewController.swift in Sources */,
450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */,
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */,
346129761FD1E0B500532771 /* WeakTimer.swift in Sources */,
346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */,
346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */,

@ -78,6 +78,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
@property (nonatomic) NSLayoutConstraint *hideDeregisteredViewConstraint;
@property (nonatomic) NSLayoutConstraint *hideArchiveReminderViewConstraint;
@property (nonatomic) NSLayoutConstraint *hideMissingContactsPermissionViewConstraint;
@property (nonatomic) NSLayoutConstraint *outageViewConstraint;
@property (nonatomic) TSThread *lastThread;
@ -166,6 +167,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
selector:@selector(deregistrationStateDidChange:)
name:DeregistrationStateDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(outageStateDidChange:)
name:OutageDetection.outageStateDidChange
object:nil];
}
- (void)dealloc
@ -198,6 +203,13 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self updateReminderViews];
}
- (void)outageStateDidChange:(id)notification
{
OWSAssertIsOnMainThread();
[self updateReminderViews];
}
#pragma mark - View Life Cycle
- (void)loadView
@ -233,6 +245,13 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
self.hideDeregisteredViewConstraint = [deregisteredView autoSetDimension:ALDimensionHeight toSize:0];
self.hideDeregisteredViewConstraint.priority = UILayoutPriorityRequired;
ReminderView *outageView = [ReminderView
nagWithText:NSLocalizedString(@"OUTAGE_WARNING", @"Label warning the user that the Signal service may be down.")
tapAction:nil];
[reminderStackView addArrangedSubview:outageView];
self.outageViewConstraint = [outageView autoSetDimension:ALDimensionHeight toSize:0];
self.outageViewConstraint.priority = UILayoutPriorityRequired;
ReminderView *archiveReminderView =
[ReminderView explanationWithText:NSLocalizedString(@"INBOX_VIEW_ARCHIVE_MODE_REMINDER",
@"Label reminding the user that they are in archive mode.")];
@ -291,16 +310,19 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
BOOL shouldHideArchiveReminderView = self.homeViewMode != HomeViewMode_Archive;
BOOL shouldHideMissingContactsPermissionView = !self.shouldShowMissingContactsPermissionView;
BOOL shouldHideDeregisteredView = !TSAccountManager.sharedInstance.isDeregistered;
BOOL shouldHideOutageView = !OutageDetection.sharedManager.hasOutage;
if (self.hideArchiveReminderViewConstraint.active == shouldHideArchiveReminderView
&& self.hideMissingContactsPermissionViewConstraint.active == shouldHideMissingContactsPermissionView
&& self.hideDeregisteredViewConstraint.active == shouldHideDeregisteredView) {
&& self.hideDeregisteredViewConstraint.active == shouldHideDeregisteredView
&& self.outageViewConstraint.active == shouldHideOutageView) {
return;
}
self.hideArchiveReminderViewConstraint.active = shouldHideArchiveReminderView;
self.hideMissingContactsPermissionViewConstraint.active = shouldHideMissingContactsPermissionView;
self.hideDeregisteredViewConstraint.active = shouldHideDeregisteredView;
self.outageViewConstraint.active = shouldHideOutageView;
[self.view setNeedsLayout];
[self.view layoutSubviews];

@ -9,11 +9,9 @@ class ReminderView: UIView {
let TAG = "[ReminderView]"
let label = UILabel()
static let defaultTapAction = {
Logger.debug("[ReminderView] tapped.")
}
typealias Action = () -> Void
var tapAction: () -> Void
var tapAction: Action?
var text: String? {
get {
@ -44,7 +42,7 @@ class ReminderView: UIView {
}
private init(mode: ReminderViewMode,
text: String, tapAction: @escaping () -> Void) {
text: String, tapAction: Action?) {
self.mode = mode
self.tapAction = tapAction
@ -55,12 +53,12 @@ class ReminderView: UIView {
setupSubviews()
}
@objc public class func nag(text: String, tapAction: @escaping () -> Void) -> ReminderView {
@objc public class func nag(text: String, tapAction: Action?) -> ReminderView {
return ReminderView(mode: .nag, text: text, tapAction: tapAction)
}
@objc public class func explanation(text: String) -> ReminderView {
return ReminderView(mode: .explanation, text: text, tapAction: ReminderView.defaultTapAction)
return ReminderView(mode: .explanation, text: text, tapAction: nil)
}
func setupSubviews() {
@ -98,8 +96,8 @@ class ReminderView: UIView {
label.autoPinEdge(toSuperviewEdge: .top)
label.autoPinEdge(toSuperviewEdge: .bottom)
guard mode == .nag else {
label.autoPinTrailingToSuperviewMargin()
// Show the disclosure indicator if this reminder has a tap action.
if tapAction == nil {
return
}
@ -121,6 +119,9 @@ class ReminderView: UIView {
}
@objc func handleTap(gestureRecognizer: UIGestureRecognizer) {
tapAction()
guard gestureRecognizer.state == .recognized else {
return
}
tapAction?()
}
}

@ -1405,6 +1405,9 @@
/* Info Message when {{other user}} updates message expiration to {{time amount}}, see the *_TIME_AMOUNT strings for context. */
"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION" = "%@ set disappearing message time to %@.";
/* Label warning the user that the Signal service may be down. */
"OUTAGE_WARNING" = "Signal is experiencing technical difficulties. We are working hard to restore service as quickly as possible.";
/* No comment provided by engineer. */
"OUTGOING_CALL" = "Outgoing call";

@ -9,6 +9,7 @@
#import "TSAccountManager.h"
#import "TSVerifyCodeRequest.h"
#import <AFNetworking/AFNetworking.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NSString *const TSNetworkManagerDomain = @"org.whispersystems.signal.networkManager";
@ -93,6 +94,8 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
}
successBlock(task, responseObject);
[OutageDetection.sharedManager reportConnectionSuccess];
};
TSNetworkManagerFailure failure = [TSNetworkManager errorPrettifyingForFailureBlock:failureBlock request:request];
@ -151,7 +154,10 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
return ^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull networkError) {
NSInteger statusCode = [task statusCode];
NSError *error = [self errorWithHTTPCode:statusCode
[OutageDetection.sharedManager reportConnectionFailure];
NSError *error = [self errorWithHTTPCode:statusCode
description:nil
failureReason:nil
recoverySuggestion:nil

@ -0,0 +1,128 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import os
@objc
public class OutageDetection: NSObject {
@objc(sharedManager)
public static let shared = OutageDetection()
@objc public static let outageStateDidChange = Notification.Name("OutageStateDidChange")
// These properties should only be accessed on the main thread.
@objc
public var hasOutage = false {
didSet {
SwiftAssertIsOnMainThread(#function)
if hasOutage != oldValue {
Logger.info("\(self.logTag) hasOutage: \(hasOutage).")
NotificationCenter.default.postNotificationNameAsync(OutageDetection.outageStateDidChange, object: nil)
}
}
}
private var shouldCheckForOutage = false {
didSet {
SwiftAssertIsOnMainThread(#function)
ensureCheckTimer()
}
}
// We only show the outage warning when we're certain there's an outage.
// DNS lookup failures, etc. are not considered an outage.
private func checkForOutageSync() -> Bool {
let host = CFHostCreateWithName(nil, "uptime.signal.org" as CFString).takeRetainedValue()
CFHostStartInfoResolution(host, .addresses, nil)
var success: DarwinBoolean = false
guard let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? else {
Logger.error("\(logTag) CFHostGetAddressing failed: no addresses.")
return false
}
guard success.boolValue else {
Logger.error("\(logTag) CFHostGetAddressing failed.")
return false
}
var isOutageDetected = false
for case let address as NSData in addresses {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if getnameinfo(address.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(address.length),
&hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
let addressString = String(cString: hostname)
let kHealthyAddress = "127.0.0.1"
let kOutageAddress = "127.0.0.2"
if addressString == kHealthyAddress {
// Do nothing.
} else if addressString == kOutageAddress {
isOutageDetected = true
} else {
owsFail("\(logTag) unexpected address: \(addressString)")
}
}
}
return isOutageDetected
}
private func checkForOutageAsync() {
Logger.info("\(self.logTag) \(#function).")
DispatchQueue.global().async {
let isOutageDetected = self.checkForOutageSync()
DispatchQueue.main.async {
self.hasOutage = isOutageDetected
}
}
}
private var checkTimer: Timer?
private func ensureCheckTimer() {
// Only monitor for outages in the main app.
guard CurrentAppContext().isMainApp else {
return
}
if shouldCheckForOutage {
if checkTimer != nil {
// Already has timer.
return
}
// The TTL of the DNS record is 60 seconds.
checkTimer = WeakTimer.scheduledTimer(timeInterval: 60, target: self, userInfo: nil, repeats: true) { [weak self] _ in
SwiftAssertIsOnMainThread(#function)
guard CurrentAppContext().isMainAppAndActive else {
return
}
guard let strongSelf = self else {
return
}
strongSelf.checkForOutageAsync()
}
} else {
checkTimer?.invalidate()
checkTimer = nil
}
}
@objc
public func reportConnectionSuccess() {
SwiftAssertIsOnMainThread(#function)
shouldCheckForOutage = false
hasOutage = false
}
@objc
public func reportConnectionFailure() {
SwiftAssertIsOnMainThread(#function)
shouldCheckForOutage = true
}
}

@ -24,6 +24,7 @@
#import "TextSecureKitEnv.h"
#import "Threading.h"
#import "WebSocketResources.pb.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -677,6 +678,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
// If socket opens, we know we're not de-registered.
[TSAccountManager.sharedInstance setIsDeregistered:NO];
[OutageDetection.sharedManager reportConnectionSuccess];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
@ -822,6 +825,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
// Otherwise clean up and align state.
[self applyDesiredSocketState];
}
[OutageDetection.sharedManager reportConnectionFailure];
}
- (void)webSocket:(SRWebSocket *)webSocket

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
/**
Loading…
Cancel
Save