import 'dart:async'; import 'dart:io'; import 'package:tools/models/device.dart'; int _ipToInt(String ip) { final parts = ip.split('.').map(int.parse).toList(); return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]; } String _intToIp(int value) { return '${(value >> 24) & 0xFF}.' '${(value >> 16) & 0xFF}.' '${(value >> 8) & 0xFF}.' '${value & 0xFF}'; } Future _isHostAlive( String ip, { int port = 80, int timeoutMs = 300, }) async { try { final socket = await Socket.connect( ip, port, timeout: Duration(milliseconds: timeoutMs), ); await socket.close(); return true; } catch (_) { return false; } } Future isHostAlive(String ip, {int port = 22, int timeoutMs = 500}) { return _isHostAlive(ip, port: port, timeoutMs: timeoutMs); } Future> scanSelectedDevicesOnline( List devices, { int port = 22, int timeoutMs = 500, }) async { final futures = >>[]; for (final device in devices) { futures.add( isHostAlive( device.address, port: port, timeoutMs: timeoutMs, ).then((isOnline) => MapEntry(device.address, isOnline)), ); } final entries = await Future.wait(futures); return Map.fromEntries(entries); } String? _extractMac(String source) { final lladdrMatch = RegExp( r'lladdr\s+([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})', caseSensitive: false, ).firstMatch(source); if (lladdrMatch != null) { return lladdrMatch.group(1)?.toLowerCase(); } final genericMatch = RegExp( r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})', caseSensitive: false, ).firstMatch(source); return genericMatch?.group(1)?.toLowerCase(); } Future _resolveMacAddress(String ip) async { try { final ipResult = await Process.run('ip', ['neigh', 'show', ip]); final ipOutput = '${ipResult.stdout}\n${ipResult.stderr}'; final ipMac = _extractMac(ipOutput); if (ipMac != null) { return ipMac; } } catch (_) { // ignore and fallback } try { final arpResult = await Process.run('arp', ['-n', ip]); final arpOutput = '${arpResult.stdout}\n${arpResult.stderr}'; return _extractMac(arpOutput); } catch (_) { return null; } } Future _scanHost( String host, { required bool Function() isCancelled, }) async { if (isCancelled()) { return null; } final isAlive = await _isHostAlive(host, port: 80); if (!isAlive || isCancelled()) { return null; } final mac = await _resolveMacAddress(host); return Device(address: host, mac: mac ?? ''); } Future scanSubnet( String net, String mask, StreamController controller, { bool Function()? isCancelled, int maxConcurrent = 64, }) async { final shouldCancel = isCancelled ?? () => false; final ipInt = _ipToInt(net); final maskInt = _ipToInt(mask); final network = ipInt & maskInt; final broadcast = network | (~maskInt & 0xFFFFFFFF); if (maxConcurrent < 1) { maxConcurrent = 1; } for (int start = network + 1; start < broadcast; start += maxConcurrent) { if (shouldCancel()) { return; } final end = (start + maxConcurrent) < broadcast ? (start + maxConcurrent) : broadcast; final futures = >[]; for (int addr = start; addr < end; addr++) { futures.add(_scanHost(_intToIp(addr), isCancelled: shouldCancel)); } final foundDevices = await Future.wait(futures); for (final device in foundDevices) { if (device == null || shouldCancel() || controller.isClosed) { continue; } controller.add(device); } } }