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
|
import Foundation
import Network
struct ReachabilityService {
static func check(domain: String, port: UInt16) async -> PortReachability {
await withCheckedContinuation { continuation in
let host = NWEndpoint.Host(domain)
let nwPort = NWEndpoint.Port(rawValue: port)!
let connection = NWConnection(host: host, port: nwPort, using: .tcp)
let context = ConnectionContext(port: port, connection: connection, continuation: continuation)
connection.stateUpdateHandler = { state in
switch state {
case .ready:
context.finish(reachable: true)
case .failed, .cancelled:
context.finish(reachable: false)
default:
break
}
}
let queue = DispatchQueue(label: "reachability.\(port)")
connection.start(queue: queue)
queue.asyncAfter(deadline: .now() + 5) {
context.finish(reachable: false)
}
}
}
static func checkAll(domain: String) async -> ServiceResult<[PortReachability]> {
async let port443 = check(domain: domain, port: 443)
async let port80 = check(domain: domain, port: 80)
let results = await [port443, port80]
return results.isEmpty ? .empty("No reachability results") : .success(results)
}
}
private final class ConnectionContext: @unchecked Sendable {
private let port: UInt16
private let connection: NWConnection
private let continuation: CheckedContinuation<PortReachability, Never>
private let start = CFAbsoluteTimeGetCurrent()
private let lock = NSLock()
private nonisolated(unsafe) var resumed = false
init(port: UInt16, connection: NWConnection, continuation: CheckedContinuation<PortReachability, Never>) {
self.port = port
self.connection = connection
self.continuation = continuation
}
nonisolated func finish(reachable: Bool) {
lock.lock()
guard !resumed else {
lock.unlock()
return
}
resumed = true
lock.unlock()
let elapsed = CFAbsoluteTimeGetCurrent() - start
let ms = reachable ? Int(elapsed * 1000) : nil
connection.cancel()
continuation.resume(returning: PortReachability(port: port, reachable: reachable, latencyMs: ms))
}
}
|