forked from home-assistant/iOS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMacBridgeNetworkMonitor.swift
117 lines (101 loc) · 4.42 KB
/
MacBridgeNetworkMonitor.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import CoreWLAN
import Foundation
import SystemConfiguration
class MacBridgeNetworkMonitor {
static var connectivityDidChangeNotification: Notification.Name = .init("ha_connectivityDidChange")
private let wifiClient: CWWiFiClient
private var scStore: SCDynamicStore!
private var cachedNetworkConnectivity: MacBridgeNetworkConnectivityImpl?
private static let networkKey = SCDynamicStoreKeyCreateNetworkGlobalEntity(
nil,
kSCDynamicStoreDomainState,
kSCEntNetIPv4
)
init() {
self.wifiClient = CWWiFiClient.shared()
let callback: SCDynamicStoreCallBack = { _, _, context in
guard let context = context else { return }
let this = Unmanaged<MacBridgeNetworkMonitor>.fromOpaque(context).takeUnretainedValue()
this.storeDidChange()
}
var context: SCDynamicStoreContext = .init(
version: 0,
info: Unmanaged.passUnretained(self).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil
)
let scStore = SCDynamicStoreCreate(nil, "HAMacBridge" as CFString, callback, &context)!
SCDynamicStoreSetNotificationKeys(scStore, nil, [Self.networkKey] as CFArray)
SCDynamicStoreSetDispatchQueue(scStore, DispatchQueue.main)
self.scStore = scStore
}
var networkConnectivity: MacBridgeNetworkConnectivityImpl {
if let cachedNetworkConnectivity = cachedNetworkConnectivity {
return cachedNetworkConnectivity
} else {
let new = newNetworkConnectivity()
cachedNetworkConnectivity = new
return new
}
}
private var currentInterface: SCNetworkInterface? {
guard let properties = SCDynamicStoreCopyValue(scStore, Self.networkKey) as? [CFString: Any] else {
return nil
}
guard let interfaceName = properties[kSCDynamicStorePropNetPrimaryInterface] as? String,
let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else {
return nil
}
guard var interface = interfaces.first(where: {
SCNetworkInterfaceGetBSDName($0) == interfaceName as CFString
}) else {
return nil
}
while let next = SCNetworkInterfaceGetInterface(interface) {
// go down to the leaf node if this is a virtual/layered interface
interface = next
}
return interface
}
private func storeDidChange() {
// we cache mainly so that we don't need to dig into the system to get the values on each access,
// not because it notifies unnecessarily (it doesn't)
cachedNetworkConnectivity = newNetworkConnectivity()
NotificationCenter.default.post(name: Self.connectivityDidChangeNotification, object: nil)
}
internal func newNetworkConnectivity() -> MacBridgeNetworkConnectivityImpl {
let primaryInterface = currentInterface
let wifiInterfaces = wifiClient.interfaces() ?? []
let wifi: MacBridgeWiFiImpl? = wifiInterfaces.compactMap { interface -> MacBridgeWiFiImpl? in
if let ssid = interface.ssid(), let bssid = interface.bssid() {
return MacBridgeWiFiImpl(ssid: ssid, bssid: bssid)
} else {
return nil
}
}.first
let type: MacBridgeNetworkType = {
if let interfaceType = primaryInterface.flatMap(SCNetworkInterfaceGetInterfaceType) {
return networkType(for: interfaceType)
} else {
return wifi != nil ? .wifi : .noNetwork
}
}()
let interface: MacBridgeNetworkInterfaceImpl? = primaryInterface.flatMap {
if let localizedName = SCNetworkInterfaceGetLocalizedDisplayName($0),
let hardwareAddress = SCNetworkInterfaceGetHardwareAddressString($0) {
return .init(name: localizedName as String, hardwareAddress: hardwareAddress as String)
} else {
return nil
}
}
return .init(networkType: type, hasWiFi: !wifiInterfaces.isEmpty, wifi: wifi, interface: interface)
}
private func networkType(for interfaceType: CFString) -> MacBridgeNetworkType {
switch interfaceType {
case kSCNetworkInterfaceTypeIEEE80211: return .wifi
case kSCNetworkInterfaceTypeEthernet: return .ethernet
default: return .unknown
}
}
}