import 'dart:async'; import 'dart:io'; import '../models/server_profile.dart'; class ProbeResult { final List addresses; final int tcpMs; // DNS lookup time and the address the TCP connection was made to. final int? dnsMs; final InternetAddress? connected; ProbeResult(this.addresses, this.tcpMs, {this.dnsMs, this.connected}); } /// A probe that failed in a known stage, so the UI can say which test failed. class ProbeFailure implements Exception { final String stage; // 'dns' or 'tcp' final Object cause; final List addresses; final int? dnsMs; const ProbeFailure( this.stage, this.cause, { this.addresses = const [], this.dnsMs, }); @override String toString() => 'ProbeFailure($stage, $cause)'; } class Diagnostics { Future probe( String host, int port, { InternetAddressType type = InternetAddressType.any, }) async { normalizeHost(host); final dnsWatch = Stopwatch()..start(); final List addresses; try { addresses = await InternetAddress.lookup( host, type: type, ).timeout(const Duration(seconds: 10)); } catch (e) { throw ProbeFailure('dns', e); } final dnsMs = dnsWatch.elapsedMilliseconds; final names = addresses.map((a) => a.address).toList(); final deadline = DateTime.now().add(const Duration(seconds: 10)); Object lastError = const SocketException('No address for requested family'); for (final address in addresses) { final remaining = deadline.difference(DateTime.now()); if (remaining <= Duration.zero) { throw ProbeFailure( 'tcp', TimeoutException('TCP'), addresses: names, dnsMs: dnsMs, ); } final watch = Stopwatch()..start(); try { final socket = await Socket.connect(address, port, timeout: remaining); socket.destroy(); return ProbeResult( names, watch.elapsedMilliseconds, dnsMs: dnsMs, connected: address, ); } catch (e) { lastError = e; } } throw ProbeFailure('tcp', lastError, addresses: names, dnsMs: dnsMs); } }