|  |  |  | // | 
					
						
							|  |  |  | //  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 { | 
					
						
							|  |  |  |             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 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |