// // 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 { AssertIsOnMainThread() if hasOutage != oldValue { Logger.info("hasOutage: \(hasOutage).") NotificationCenter.default.postNotificationNameAsync(OutageDetection.outageStateDidChange, object: nil) } } } private var shouldCheckForOutage = false { didSet { // Loki: Don't check for outages // AssertIsOnMainThread() // 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("CFHostGetAddressing failed: no addresses.") return false } guard success.boolValue else { Logger.error("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 { owsFailDebug("unexpected address: \(addressString)") } } } return isOutageDetected } private func checkForOutageAsync() { Logger.info("") 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 AssertIsOnMainThread() guard CurrentAppContext().isMainAppAndActive else { return } guard let strongSelf = self else { return } strongSelf.checkForOutageAsync() } } else { checkTimer?.invalidate() checkTimer = nil } } @objc public func reportConnectionSuccess() { DispatchMainThreadSafe { self.shouldCheckForOutage = false self.hasOutage = false } } @objc public func reportConnectionFailure() { DispatchMainThreadSafe { self.shouldCheckForOutage = true } } }