diff options
| author | Christian Cleberg <[email protected]> | 2026-04-20 15:28:12 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-20 15:28:12 -0500 |
| commit | ee2520f2c0c2d806fd284c3d6b8a81a3822f5270 (patch) | |
| tree | 0680550a1071e01546ba074c2f20b1bf5a452238 /DomainDig/PortScanService.swift | |
| parent | 4e94ee723cd19c964b1cf37792931e0c7c4614f4 (diff) | |
| download | domain-dig-ee2520f2c0c2d806fd284c3d6b8a81a3822f5270.tar.gz domain-dig-ee2520f2c0c2d806fd284c3d6b8a81a3822f5270.tar.bz2 domain-dig-ee2520f2c0c2d806fd284c3d6b8a81a3822f5270.zip | |
feat(v1.8): improve result structure, history, and consistency
- normalize sections and add summary card
- fix resolver usage in reverse DNS
- include custom port scans in history/export
- standardize error handling
- decompose ContentView into smaller views
Diffstat (limited to 'DomainDig/PortScanService.swift')
| -rw-r--r-- | DomainDig/PortScanService.swift | 54 |
1 files changed, 41 insertions, 13 deletions
diff --git a/DomainDig/PortScanService.swift b/DomainDig/PortScanService.swift index 4dc9001..36c2827 100644 --- a/DomainDig/PortScanService.swift +++ b/DomainDig/PortScanService.swift @@ -20,12 +20,18 @@ struct PortScanService { PortInfo(port: 8443, service: "HTTPS Alt"), ] - static func scanAll(domain: String) async -> [PortScanResult] { + static func scanAll(domain: String) async -> ServiceResult<[PortScanResult]> { await withTaskGroup(of: PortScanResult.self, returning: [PortScanResult].self) { group in for info in ports { group.addTask { - let open = await probe(domain: domain, port: info.port) - return PortScanResult(port: info.port, service: info.service, open: open) + let result = await probe(domain: domain, port: info.port) + return PortScanResult( + port: info.port, + service: info.service, + open: result.open, + kind: .standard, + durationMs: result.durationMs + ) } } @@ -35,20 +41,24 @@ struct PortScanService { } // Sort by port number - return results.sorted { $0.port < $1.port } + let sorted = results.sorted { $0.port < $1.port } + return sorted.isEmpty ? [] : sorted } + .pipe { $0.isEmpty ? .empty("No port scan results") : .success($0) } } - static func scanPorts(domain: String, ports: [UInt16], timeout: TimeInterval) async -> [PortScanResult] { + static func scanPorts(domain: String, ports: [UInt16], timeout: TimeInterval) async -> ServiceResult<[PortScanResult]> { await withTaskGroup(of: PortScanResult.self, returning: [PortScanResult].self) { group in for port in ports { let service = self.ports.first(where: { $0.port == port })?.service ?? "Custom" group.addTask { - let open = await probe(domain: domain, port: port, timeout: timeout) + let result = await probe(domain: domain, port: port, timeout: timeout) return PortScanResult( port: port, service: service, - open: open + open: result.open, + kind: .custom, + durationMs: result.durationMs ) } } @@ -58,8 +68,10 @@ struct PortScanService { results.append(result) } - return results.sorted { $0.port < $1.port } + let sorted = results.sorted { $0.port < $1.port } + return sorted.isEmpty ? [] : sorted } + .pipe { $0.isEmpty ? .empty("No custom port scan results") : .success($0) } } static func grabBanner(host: String, port: UInt16, timeout: TimeInterval = 3.0) async -> String? { @@ -111,11 +123,11 @@ struct PortScanService { } } - private static func probe(domain: String, port: UInt16) async -> Bool { + private static func probe(domain: String, port: UInt16) async -> PortProbeResult { await probe(domain: domain, port: port, timeout: 5) } - private static func probe(domain: String, port: UInt16, timeout: TimeInterval) async -> Bool { + private static func probe(domain: String, port: UInt16, timeout: TimeInterval) async -> PortProbeResult { await withCheckedContinuation { continuation in let host = NWEndpoint.Host(domain) let nwPort = NWEndpoint.Port(rawValue: port)! @@ -143,13 +155,19 @@ struct PortScanService { } } +private struct PortProbeResult: Sendable { + let open: Bool + let durationMs: Int? +} + private final class ProbeContext: @unchecked Sendable { private let connection: NWConnection - private let continuation: CheckedContinuation<Bool, Never> + private let continuation: CheckedContinuation<PortProbeResult, Never> + private let start = CFAbsoluteTimeGetCurrent() private let lock = NSLock() private nonisolated(unsafe) var resumed = false - init(connection: NWConnection, continuation: CheckedContinuation<Bool, Never>) { + init(connection: NWConnection, continuation: CheckedContinuation<PortProbeResult, Never>) { self.connection = connection self.continuation = continuation } @@ -164,7 +182,17 @@ private final class ProbeContext: @unchecked Sendable { lock.unlock() connection.cancel() - continuation.resume(returning: open) + let elapsedMs = Int((CFAbsoluteTimeGetCurrent() - start) * 1000) + continuation.resume(returning: PortProbeResult( + open: open, + durationMs: elapsedMs >= 0 ? elapsedMs : nil + )) + } +} + +private extension Array { + func pipe<T>(_ transform: (Self) -> T) -> T { + transform(self) } } |
