From 5ffdb99901780526c03d4827c910bc1116166479 Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:01:37 +0530 Subject: [PATCH 1/6] Major upgrade via sembast db and mac-vendor csv --- .gitignore | 5 +- CHANGELOG.md | 6 ++ example/host_scan.dart | 9 +- example/mdns_scan.dart | 1 + example/port_scan.dart | 39 ++++--- lib/injectable.config.dart | 32 ++++++ lib/injectable.dart | 12 +++ lib/network_tools.dart | 28 ++++- lib/src/device_info/arp_table.dart | 100 ------------------ lib/src/device_info/arp_table_helper.dart | 49 +++++++++ lib/src/device_info/net_interface.dart | 5 +- lib/src/device_info/vendor_table.dart | 52 +++++---- lib/src/host_scanner.dart | 26 +++-- lib/src/models/active_host.dart | 10 +- lib/src/models/arp_data.dart | 27 +++-- lib/src/models/open_port.dart | 20 ++-- lib/src/models/vendor.dart | 7 ++ lib/src/services/arp_service.dart | 9 ++ .../impls/arp_service_sembast_impl.dart | 61 +++++++++++ pubspec.yaml | 14 ++- test/network_tools_test.dart | 12 +-- 21 files changed, 337 insertions(+), 187 deletions(-) create mode 100644 lib/injectable.config.dart create mode 100644 lib/injectable.dart delete mode 100644 lib/src/device_info/arp_table.dart create mode 100644 lib/src/device_info/arp_table_helper.dart create mode 100644 lib/src/services/arp_service.dart create mode 100644 lib/src/services/impls/arp_service_sembast_impl.dart diff --git a/.gitignore b/.gitignore index 2759cc6..55f4b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ **/build/ # Generated sources -*.g.dart \ No newline at end of file +*.g.dart + +# Downloaded files +mac-vendors-export.csv \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef69ed..13b98a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 4.0.0 +### Breaking changes + +1. Call configureNetworkTools in your main function + + ## 3.2.7 1. Removed example folder and added pubignore. diff --git a/example/host_scan.dart b/example/host_scan.dart index fed436e..6e526e7 100644 --- a/example/host_scan.dart +++ b/example/host_scan.dart @@ -1,14 +1,9 @@ import 'package:logging/logging.dart'; import '../lib/network_tools.dart'; +import '../lib/src/network_tools_utils.dart'; void main() async { - Logger.root.level = Level.FINE; - Logger.root.onRecord.listen((record) { - print( - '${record.time.toLocal()}: ${record.level.name}: ${record.loggerName}: ${record.message}', - ); - }); - final log = Logger("host_scan_example"); + await configureNetworkTools(); String subnet = '192.168.0'; //Default network id for home networks diff --git a/example/mdns_scan.dart b/example/mdns_scan.dart index 7dc0568..0477c25 100644 --- a/example/mdns_scan.dart +++ b/example/mdns_scan.dart @@ -1,6 +1,7 @@ import 'package:network_tools/network_tools.dart'; Future main() async { + await configureNetworkTools(); for (final ActiveHost activeHost in await MdnsScanner.searchMdnsDevices()) { final MdnsInfo? mdnsInfo = await activeHost.mdnsInfo; print( diff --git a/example/port_scan.dart b/example/port_scan.dart index 5579d7c..2f700d0 100644 --- a/example/port_scan.dart +++ b/example/port_scan.dart @@ -1,28 +1,25 @@ import 'package:logging/logging.dart'; +import '../lib/src/network_tools_utils.dart'; import 'package:network_tools/network_tools.dart'; -void main() { - final log = Logger("port-scan"); - Logger.root.level = Level.FINE; - Logger.root.onRecord.listen((record) { - print( - '${record.time.toLocal()}: ${record.level.name}: ${record.loggerName}: ${record.message}', - ); - }); +void main() async { + await configureNetworkTools(); - const String address = '192.168.1.1'; - // or You can also get address using network_info_plus package - // final String? address = await (NetworkInfo().getWifiIP()); - final String subnet = address.substring(0, address.lastIndexOf('.')); + String subnet = '192.168.0'; //Default network id for home networks + + final interface = await NetInterface.localInterface(); + final netId = interface?.networkId; + if (netId != null) { + subnet = netId; + log.fine('subnet id $subnet'); + } // [New] Scan for a single open port in a subnet // You can set [firstHostId] and scan will start from this host in the network. // Similarly set [lastHostId] and scan will end at this host in the network. final stream2 = HostScanner.scanDevicesForSinglePort( subnet, - 53, - // firstHostId: 1, - // lastHostId: 254, + 22, progressCallback: (progress) { log.finer('Progress for port discovery on host : $progress'); }, @@ -30,10 +27,12 @@ void main() { stream2.listen( (activeHost) { + log.fine( + '[scanDevicesForSinglePort]: Found device : ${activeHost.toString()}'); final OpenPort deviceWithOpenPort = activeHost.openPorts[0]; if (deviceWithOpenPort.isOpen) { log.fine( - 'Found open port: ${deviceWithOpenPort.port} on ${activeHost.address}', + '[scanDevicesForSinglePort]: Found open port: ${deviceWithOpenPort.port} on ${activeHost.address}', ); } }, @@ -42,7 +41,13 @@ void main() { }, ); // Don't forget to cancel the stream when not in use. - const String target = '192.168.1.1'; + String target = '192.168.1.1'; + final addr = interface?.ipAddress; + if (addr != null) { + target = addr; + log.fine("Target is $target"); + } + PortScanner.scanPortsForSingleDevice( target, // Scan will start from this port. diff --git a/lib/injectable.config.dart b/lib/injectable.config.dart new file mode 100644 index 0000000..bc529b4 --- /dev/null +++ b/lib/injectable.config.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_lambdas +// ignore_for_file: lines_longer_than_80_chars +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:get_it/get_it.dart' as _i1; +import 'package:injectable/injectable.dart' as _i2; + +import 'src/services/arp_service.dart' as _i3; +import 'src/services/impls/arp_service_sembast_impl.dart' as _i4; + +extension GetItInjectableX on _i1.GetIt { +// initializes the registration of main-scope dependencies inside of GetIt + _i1.GetIt init({ + String? environment, + _i2.EnvironmentFilter? environmentFilter, + }) { + final gh = _i2.GetItHelper( + this, + environment, + environmentFilter, + ); + gh.factory<_i3.ARPService>(() => _i4.ARPServiceSembastImpl()); + return this; + } +} diff --git a/lib/injectable.dart b/lib/injectable.dart new file mode 100644 index 0000000..4be767e --- /dev/null +++ b/lib/injectable.dart @@ -0,0 +1,12 @@ +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; +import 'package:network_tools/injectable.config.dart'; + +final getIt = GetIt.instance; + +@InjectableInit( + initializerName: 'init', // default + preferRelativeImports: true, // default + asExtension: true, // default +) +void configureDependencies() => getIt.init(); diff --git a/lib/network_tools.dart b/lib/network_tools.dart index 728501c..c0e686f 100644 --- a/lib/network_tools.dart +++ b/lib/network_tools.dart @@ -1,7 +1,12 @@ /// Network tools base library library network_tools; -export 'src/device_info/arp_table.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logging/logging.dart'; +import 'package:network_tools/injectable.dart'; +import 'package:network_tools/src/device_info/vendor_table.dart'; +import 'package:network_tools/src/services/arp_service.dart'; + export 'src/device_info/net_interface.dart'; export 'src/device_info/vendor_table.dart'; export 'src/host_scanner.dart'; @@ -14,3 +19,24 @@ export 'src/models/open_port.dart'; export 'src/models/sendable_active_host.dart'; export 'src/models/vendor.dart'; export 'src/port_scanner.dart'; + +final _getIt = GetIt.instance; +final _arpServiceFuture = _getIt().open(); + +Future configureNetworkTools({ + bool enableDebugging = false, +}) async { + if (enableDebugging) { + Logger.root.level = Level.FINE; + Logger.root.onRecord.listen((record) { + // ignore: avoid_print + print( + '${record.time.toLocal()}: ${record.level.name}: ${record.loggerName}: ${record.message}', + ); + }); + } + configureDependencies(); + + await (await _arpServiceFuture).buildTable(); + await VendorTable.createVendorTableMap(); +} diff --git a/lib/src/device_info/arp_table.dart b/lib/src/device_info/arp_table.dart deleted file mode 100644 index 6a86543..0000000 --- a/lib/src/device_info/arp_table.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:logging/logging.dart'; -import 'package:network_tools/src/models/arp_data.dart'; -import 'package:path/path.dart'; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; - -class ARPTable { - static final arpLogger = Logger("arp-table-logger"); - static const String tableName = 'ARPPackets'; - static const String columnName = 'iPAddress'; - - static Future?> entries() async { - final list = (await (await _db()).query(tableName, columns: [columnName])) - .map((e) => ARPData.fromJson(e).iPAddress.toString()) - .toList(); - return list; - } - - static Future entryFor(String address) async { - arpLogger.fine('Trying to fetch arp table entry for $address'); - final entries = (await (await _db()) - .query(tableName, where: '$columnName = ?', whereArgs: [address])) - .map((e) => ARPData.fromJson(e)) - .toList(); - if (entries.isNotEmpty) { - return entries.first; - } - return null; - } - - static Future _db() async { - sqfliteFfiInit(); - final databaseFactory = databaseFactoryFfi; - final databasesPath = await databaseFactory.getDatabasesPath(); - final path = join(databasesPath, 'localarp.db'); - return databaseFactory.openDatabase( - path, - options: OpenDatabaseOptions( - version: 4, - singleInstance: false, - onCreate: (db, version) async { - await db.execute(''' - CREATE TABLE IF NOT EXISTS $tableName ( - host TEXT, - iPAddress TEXT PRIMARY KEY UNIQUE, - macAddress TEXT, - interfaceName TEXT, - interfaceType TEXT) - '''); - }, - ), - ); - } - - static Future buildTable() async { - final database = await _db(); - final result = await Process.run('arp', ['-a']); - final entries = const LineSplitter().convert(result.stdout.toString()); - RegExp? pattern; - if (Platform.isMacOS) { - pattern = RegExp( - r'(?[\w.?]*)\s\((?.*)\)\sat\s(?.*)\son\s(?\w+)\sifscope\s*(\w*)\s*\[(?.*)\]', - ); - } else if (Platform.isLinux) { - pattern = RegExp( - r'(?[\w.?]*)\s\((?.*)\)\sat\s(?.*)\s\[(?.*)\]\son\s(?\w+)', - ); - } else { - pattern = RegExp(r'(?.*)\s(?.*)\s(?.*)'); - } - - for (final entry in entries) { - final match = pattern.firstMatch(entry); - if (match != null) { - final arpData = ARPData( - host: match.groupNames.contains('host') - ? match.namedGroup("host") - : null, - iPAddress: match.namedGroup("ip"), - macAddress: match.namedGroup("mac"), - interfaceName: match.groupNames.contains('intf') - ? match.namedGroup("intf") - : null, - interfaceType: match.namedGroup("typ"), - ); - final key = arpData.iPAddress; - if (key != null && arpData.macAddress != '(incomplete)') { - arpLogger.fine("Adding entry to table -> $arpData"); - await database.insert( - tableName, - arpData.toJson(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - } - } - } -} diff --git a/lib/src/device_info/arp_table_helper.dart b/lib/src/device_info/arp_table_helper.dart new file mode 100644 index 0000000..715dd4e --- /dev/null +++ b/lib/src/device_info/arp_table_helper.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:network_tools/src/models/arp_data.dart'; + +class ARPTableHelper { + static final arpLogger = Logger("arp-table-logger"); + + static Future> buildTable() async { + final arpEntries = []; + final result = await Process.run('arp', ['-a']); + final entries = const LineSplitter().convert(result.stdout.toString()); + RegExp? pattern; + if (Platform.isMacOS) { + pattern = RegExp( + r'(?[\w.?]*)\s\((?.*)\)\sat\s(?.*)\son\s(?\w+)\sifscope\s*(\w*)\s*\[(?.*)\]', + ); + } else if (Platform.isLinux) { + pattern = RegExp( + r'(?[\w.?]*)\s\((?.*)\)\sat\s(?.*)\s\[(?.*)\]\son\s(?\w+)', + ); + } else { + pattern = RegExp(r'(?.*)\s(?.*)\s(?.*)'); + } + + for (final entry in entries) { + final match = pattern.firstMatch(entry); + if (match != null) { + final arpData = ARPData( + hostname: match.groupNames.contains('host') + ? match.namedGroup("host") ?? '' + : '', + iPAddress: match.namedGroup("ip") ?? ARPData.nullIPAddress, + macAddress: match.namedGroup("mac") ?? ARPData.nullMacAddress, + interfaceName: match.groupNames.contains('intf') + ? match.namedGroup("intf") ?? '' + : '', + interfaceType: match.namedGroup("typ") ?? ARPData.nullInterfaceType, + ); + if (arpData.macAddress != '(incomplete)') { + arpLogger.fine("Adding entry to table -> $arpData"); + arpEntries.add(arpData); + } + } + } + return arpEntries; + } +} diff --git a/lib/src/device_info/net_interface.dart b/lib/src/device_info/net_interface.dart index f1f9ecb..09b1ab1 100644 --- a/lib/src/device_info/net_interface.dart +++ b/lib/src/device_info/net_interface.dart @@ -12,8 +12,9 @@ class NetInterface { final String ipAddress; static Future localInterface() async { - final interfaceList = - await NetworkInterface.list(); //will give interface list + final interfaceList = await NetworkInterface.list( + type: InternetAddressType.IPv4, + ); //will give interface list if (interfaceList.isNotEmpty) { final localInterface = interfaceList.first; //fetching first interface like en0/eth0 diff --git a/lib/src/device_info/vendor_table.dart b/lib/src/device_info/vendor_table.dart index 01e46da..d6454f6 100644 --- a/lib/src/device_info/vendor_table.dart +++ b/lib/src/device_info/vendor_table.dart @@ -1,39 +1,53 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:isolate'; import 'package:csv/csv.dart'; +import 'package:http/http.dart' as http; import 'package:network_tools/src/models/arp_data.dart'; import 'package:network_tools/src/models/vendor.dart'; +import 'package:network_tools/src/network_tools_utils.dart'; class VendorTable { - static Map vendorTableMap = {}; + static Map _vendorTableMap = {}; static Future getVendor(Future arpDataFuture) async { final arpData = await arpDataFuture; if (arpData != null) { - final macAddress = arpData.macAddress; - if (macAddress != null) { - if (vendorTableMap.keys.isEmpty) { - vendorTableMap = await _createVendorTableMap(); - } - final pattern = macAddress.contains(':') ? ':' : '-'; - // print("Mac address: ${macAddress.split(pattern).sublist(0, 3).join()}"); - return vendorTableMap[macAddress.split(pattern).sublist(0, 3).join()] - as Vendor?; + if (arpData.notNullMacAddress) { + await createVendorTableMap(); + final pattern = arpData.macAddress.contains(':') ? ':' : '-'; + return _vendorTableMap[ + arpData.macAddress.split(pattern).sublist(0, 3).join()] as Vendor?; } } return null; } - static Future> _createVendorTableMap() async { - final receivePort = ReceivePort(); - await Isolate.spawn(_fetchVendorTable, receivePort.sendPort); - return await receivePort.first as Map; + static Future createVendorTableMap() async { + if (_vendorTableMap.keys.isEmpty) { + _vendorTableMap = await _fetchVendorTable(); + } + return; } - static Future _fetchVendorTable(SendPort sendPort) async { - final input = File('./lib/assets/mac-vendors-export.csv').openRead(); + static Future> _fetchVendorTable() async { + //Download and store + log.fine("Downloading mac-vendors-export.csv from network_tools"); + final directory = Directory("mac-vendors-export.csv"); + if (!directory.existsSync()) { + final response = await http.get( + Uri.https( + "raw.githubusercontent.com", + "osociety/network_tools/main/lib/assets/mac-vendors-export.csv", + ), + ); + File(directory.path).writeAsBytesSync(response.bodyBytes); + log.fine("Downloaded mac-vendors-export.csv successfully"); + } else { + log.fine("File mac-vendors-export.csv already exists"); + } + + final input = File(directory.path).openRead(); List> fields = await input .transform(utf8.decoder) @@ -49,8 +63,6 @@ class VendorTable { // print('Vendor mac split : ${vendor.macPrefix.split(":").join()}'); result[vendor.macPrefix.split(":").join()] = vendor; } - - sendPort.send(result); - Isolate.exit(); + return result; } } diff --git a/lib/src/host_scanner.dart b/lib/src/host_scanner.dart index a3244c6..2dac81c 100644 --- a/lib/src/host_scanner.dart +++ b/lib/src/host_scanner.dart @@ -3,15 +3,17 @@ import 'dart:isolate'; import 'dart:math'; import 'package:dart_ping/dart_ping.dart'; -import 'package:network_tools/src/device_info/arp_table.dart'; -import 'package:network_tools/src/models/active_host.dart'; -import 'package:network_tools/src/models/callbacks.dart'; -import 'package:network_tools/src/models/sendable_active_host.dart'; +import 'package:get_it/get_it.dart'; +import 'package:network_tools/network_tools.dart'; import 'package:network_tools/src/network_tools_utils.dart'; -import 'package:network_tools/src/port_scanner.dart'; +import 'package:network_tools/src/services/arp_service.dart'; + +final _getIt = GetIt.instance; /// Scans for all hosts in a subnet. class HostScanner { + static final arpServiceFuture = _getIt().open(); + /// Devices scan will start from this integer Id static const int defaultFirstHostId = 1; @@ -32,7 +34,6 @@ class HostScanner { ProgressCallback? progressCallback, bool resultsInAddressAscendingOrder = true, }) async* { - await ARPTable.buildTable(); final stream = getAllSendablePingableDevices( subnet, firstHostId: firstHostId, @@ -102,6 +103,7 @@ class HostScanner { int timeoutInSeconds = 1, }) async { SendableActiveHost? tempSendableActivateHost; + await for (final PingData pingData in Ping(host, count: 1, timeout: timeoutInSeconds).stream) { final PingResponse? response = pingData.response; @@ -111,21 +113,29 @@ class HostScanner { if (pingError == null) { final Duration? time = response.time; if (time != null) { + log.fine("Pingable device found: $host"); tempSendableActivateHost = SendableActiveHost(host, pingData); + } else { + log.fine("Non pingable device found: $host"); } } } if (tempSendableActivateHost == null) { // Check if it's there in arp table - final data = await ARPTable.entryFor(host); + final data = await (await arpServiceFuture).entryFor(host); + if (data != null) { + log.fine("Successfully fetched arp entry for $host as $data"); tempSendableActivateHost = SendableActiveHost(host, pingData); + } else { + log.fine("Problem in fetching arp entry for $host"); } } if (tempSendableActivateHost != null) { activeHostsController.add(tempSendableActivateHost); } } + return tempSendableActivateHost; } @@ -155,7 +165,6 @@ class HostScanner { ProgressCallback? progressCallback, bool resultsInAddressAscendingOrder = true, }) async* { - await ARPTable.buildTable(); const int scanRangeForIsolate = 51; final int lastValidSubnet = validateAndGetLastValidSubnet(subnet, firstHostId, lastHostId); @@ -197,6 +206,7 @@ class HostScanner { /// Will search devices in the network inside new isolate @pragma('vm:entry-point') static Future _startSearchingDevices(SendPort sendPort) async { + await configureNetworkTools(); final port = ReceivePort(); sendPort.send(port.sendPort); diff --git a/lib/src/models/active_host.dart b/lib/src/models/active_host.dart index 8621e58..8fb0284 100644 --- a/lib/src/models/active_host.dart +++ b/lib/src/models/active_host.dart @@ -1,8 +1,12 @@ import 'package:dart_ping/dart_ping.dart'; +import 'package:get_it/get_it.dart'; import 'package:network_tools/network_tools.dart'; import 'package:network_tools/src/network_tools_utils.dart'; +import 'package:network_tools/src/services/arp_service.dart'; import 'package:universal_io/io.dart'; +final _getIt = GetIt.instance; + /// ActiveHost which implements comparable /// By default sort by hostId ascending class ActiveHost extends Comparable { @@ -87,6 +91,7 @@ class ActiveHost extends Comparable { } static const generic = 'Generic Device'; + static final arpService = _getIt(); InternetAddress internetAddress; @@ -171,14 +176,15 @@ class ActiveHost extends Comparable { Future resolveInfo() async { await arpData; - await vendor; + // await vendor; await deviceName; await mdnsInfo; await hostName; } Future setARPData() async { - return ARPTable.entryFor(address); + await arpService.open(); + return arpService.entryFor(address); } Future setVendor() async { diff --git a/lib/src/models/arp_data.dart b/lib/src/models/arp_data.dart index d21cdfd..79456c4 100644 --- a/lib/src/models/arp_data.dart +++ b/lib/src/models/arp_data.dart @@ -6,7 +6,7 @@ part 'arp_data.g.dart'; @JsonSerializable() class ARPData { ARPData({ - required this.host, + required this.hostname, required this.iPAddress, required this.macAddress, required this.interfaceName, @@ -15,23 +15,32 @@ class ARPData { factory ARPData.fromJson(Map json) => _$ARPDataFromJson(json); - final String? host; - final String? iPAddress; - final String? macAddress; - final String? interfaceName; - final String? interfaceType; + final String hostname; + final String iPAddress; + static const String primaryKeySembast = 'iPAddress'; + static const String nullIPAddress = '0.0.0.0'; + static const String nullMacAddress = 'ff:ff:ff:ff:ff:ff'; + static const String nullInterfaceType = 'ethernet'; + + final String macAddress; + final String interfaceName; + final String interfaceType; Map toJson() => _$ARPDataToJson(this); + bool get notNullIPAddress => iPAddress != nullIPAddress; + bool get notNullMacAddress => macAddress != nullMacAddress; + bool get notNullInterfaceType => interfaceType != nullInterfaceType; + @override String toString() { if (Platform.isMacOS) { - return '$host ($iPAddress) at $macAddress on $interfaceName ifscope [$interfaceType]'; + return '$hostname ($iPAddress) at $macAddress on $interfaceName ifscope [$interfaceType]'; } else if (Platform.isLinux) { - return '$host ($iPAddress) at $macAddress [$interfaceType] on $interfaceName'; + return '$hostname ($iPAddress) at $macAddress [$interfaceType] on $interfaceName'; } else if (Platform.isWindows) { return 'Internet Address: $iPAddress, Physical Address: $macAddress, Type: $interfaceType'; } - return '$host ($iPAddress) at $macAddress on $interfaceName type [$interfaceType]'; + return '$hostname ($iPAddress) at $macAddress on $interfaceName type [$interfaceType]'; } } diff --git a/lib/src/models/open_port.dart b/lib/src/models/open_port.dart index 9532d25..6381b20 100644 --- a/lib/src/models/open_port.dart +++ b/lib/src/models/open_port.dart @@ -1,19 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'open_port.g.dart'; + /// Represents open port for a target Address +@JsonSerializable() class OpenPort extends Comparable { - OpenPort(this._port, {this.isOpen = true}); + OpenPort(this.port, {this.isOpen = true}); + factory OpenPort.fromJson(Map json) => + _$OpenPortFromJson(json); - final int _port; + final int port; final bool isOpen; - int get port => _port; - @override int compareTo(OpenPort other) { - return _port.compareTo(other.port); + return port.compareTo(other.port); } @override - int get hashCode => _port.hashCode; + int get hashCode => port.hashCode; @override bool operator ==(Object other) { @@ -23,6 +27,8 @@ class OpenPort extends Comparable { @override String toString() { - return _port.toString(); + return port.toString(); } + + Map toJson() => _$OpenPortToJson(this); } diff --git a/lib/src/models/vendor.dart b/lib/src/models/vendor.dart index 6557ab7..cd23ec7 100644 --- a/lib/src/models/vendor.dart +++ b/lib/src/models/vendor.dart @@ -1,4 +1,8 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'vendor.g.dart'; + /// Gives vendor details matching as prefix of mac address +@JsonSerializable() class Vendor { Vendor({ required this.macPrefix, @@ -17,6 +21,9 @@ class Vendor { lastUpdate: csvField[4] as String, ); } + factory Vendor.fromJson(Map json) => _$VendorFromJson(json); + + Map toJson() => _$VendorToJson(this); final String macPrefix; final String vendorName; diff --git a/lib/src/services/arp_service.dart b/lib/src/services/arp_service.dart new file mode 100644 index 0000000..911c8be --- /dev/null +++ b/lib/src/services/arp_service.dart @@ -0,0 +1,9 @@ +import 'package:network_tools/src/models/arp_data.dart'; + +abstract class ARPService { + Future open(); + Future?> entries(); + Future entryFor(String address); + Future buildTable(); + void close(); +} diff --git a/lib/src/services/impls/arp_service_sembast_impl.dart b/lib/src/services/impls/arp_service_sembast_impl.dart new file mode 100644 index 0000000..34323fe --- /dev/null +++ b/lib/src/services/impls/arp_service_sembast_impl.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:injectable/injectable.dart'; +import 'package:network_tools/src/device_info/arp_table_helper.dart'; +import 'package:network_tools/src/models/arp_data.dart'; +import 'package:network_tools/src/services/arp_service.dart'; +import 'package:sembast/sembast.dart'; +import 'package:sembast/sembast_io.dart'; + +@Injectable(as: ARPService) +class ARPServiceSembastImpl extends ARPService { + static Database? _db; + static final _store = stringMapStoreFactory.store('arpstore'); + + @override + Future buildTable() async { + final entries = + (await ARPTableHelper.buildTable()).map((e) => e.toJson()).toList(); + await _store.addAll(_db!, entries); + } + + @override + void close() { + _db?.close(); + } + + @override + Future?> entries() async { + return (await (_store.find( + _db!, + finder: Finder(sortOrders: [SortOrder(ARPData.primaryKeySembast)]), + ) as FutureOr>>>)) + .map((e) => e.key) + .toList(); + } + + @override + Future entryFor(String address) async { + final records = await _store.find( + _db!, + finder: Finder( + filter: Filter.equals(ARPData.primaryKeySembast, address), + ), + ); + + if (records.isNotEmpty) { + return ARPData.fromJson( + records[0].value, + ); + } + return null; + } + + @override + Future open() async { + if (_db != null) return Future.value(this); + final dbFactory = databaseFactoryIo; + _db = await dbFactory.openDatabase('build/network_tools.db'); + return Future.value(this); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f870e15..0d24c1f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: network_tools description: Networking Tools library which can help you discover open ports, devices on subnet and many other things. -version: 3.2.7 +version: 4.0.0 issue_tracker: https://github.com/osociety/network_tools/issues repository: https://github.com/osociety/network_tools @@ -24,6 +24,12 @@ dependencies: # Multi-platform network ping utility. dart_ping: ^9.0.0 # Defines the annotations used by json_serializable + get_it: ^7.6.0 + # A composable, Future-based library for making HTTP requests. + http: ^1.1.0 + # Injectable is a convenient code generator for get_it. + injectable: ^2.2.0 + # Defines the annotations used by json_serializable. json_annotation: ^4.8.1 # Debugging and error logging. logging: ^1.2.0 @@ -33,8 +39,8 @@ dependencies: path: ^1.8.3 # Process run helpers process_run: ^0.13.1 - # sqflite based ffi implementation - sqflite_common_ffi: ^2.3.0+2 + # Yet another NoSQL persistent store database solution for single process io apps. + sembast: ^3.5.0+1 # Cross-platform 'dart:io' that works in all platforms. universal_io: ^2.0.4 @@ -42,6 +48,8 @@ dependencies: dev_dependencies: # Standalone generator and watcher for Dart build_runner: ^2.4.6 + # A generator for injectable library. + injectable_generator: ^2.4.0 # Provides Dart Build System builders for handling JSON. json_serializable: ^6.7.1 # Set of lint rules for Dart. diff --git a/test/network_tools_test.dart b/test/network_tools_test.dart index a462192..a97b840 100644 --- a/test/network_tools_test.dart +++ b/test/network_tools_test.dart @@ -1,19 +1,10 @@ import 'dart:async'; -import 'package:logging/logging.dart'; import 'package:network_tools/network_tools.dart'; +import 'package:network_tools/src/network_tools_utils.dart'; import 'package:test/test.dart'; import 'package:universal_io/io.dart'; void main() { - final log = Logger("host_scan_test"); - - // Logger.root.level = Level.FINE; - // Logger.root.onRecord.listen((record) { - // // print( - // // '${DateFormat.Hms().format(record.time)}: ${record.level.name}: ${record.loggerName}: ${record.message}', - // // ); - // }); - int port = 0; int firstHostId = 0; int lastHostId = 0; @@ -25,6 +16,7 @@ void main() { late ServerSocket server; // Fetching interfaceIp and hostIp setUpAll(() async { + await configureNetworkTools(); //open a port in shared way because of portscanner using same, //if passed false then two hosts come up in search and breaks test. server = From adb434c744ec8fbff4401d29e12712eb8283e967 Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:05:51 +0530 Subject: [PATCH 2/6] ignore analyzer warning --- README.md | 12 +++++++++++- lib/injectable.config.dart | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e6c328..cece781 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,20 @@ Partly Work: Please check [network_tools_flutter](https://github.com/osociety/network_tools_flutter) package for extensive support to features on different platforms. -## Import package in your app +## Import package in your app ```dart import 'package:network_tools/network_tools.dart'; + +``` + +## Configure network tools in main function + +```dart +Future main() async { + await configureNetworkTools(enableDebugging: true); + runApp(const MyApp()); +} ``` ## Usage diff --git a/lib/injectable.config.dart b/lib/injectable.config.dart index bc529b4..84adf4c 100644 --- a/lib/injectable.config.dart +++ b/lib/injectable.config.dart @@ -9,10 +9,12 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes + import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; - +// ignore: always_use_package_imports import 'src/services/arp_service.dart' as _i3; +// ignore: always_use_package_imports import 'src/services/impls/arp_service_sembast_impl.dart' as _i4; extension GetItInjectableX on _i1.GetIt { From ce0eaca84613f9cbb1552bd6bcd3b5e13e4dd083 Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:07:58 +0530 Subject: [PATCH 3/6] exclude .config.dart file --- analysis_options.yaml | 1 + lib/injectable.config.dart | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index fa699ff..8394ed6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,6 +15,7 @@ analyzer: exclude: - "**/*.g.dart" - "**/*.freezed.dart" + - "**/*.config.dart" - "**/*.pb.dart" - "**/*.pbenum.dart" - "**/*.pbgrpc.dart" diff --git a/lib/injectable.config.dart b/lib/injectable.config.dart index 84adf4c..bc529b4 100644 --- a/lib/injectable.config.dart +++ b/lib/injectable.config.dart @@ -9,12 +9,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes - import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; -// ignore: always_use_package_imports + import 'src/services/arp_service.dart' as _i3; -// ignore: always_use_package_imports import 'src/services/impls/arp_service_sembast_impl.dart' as _i4; extension GetItInjectableX on _i1.GetIt { From 7bac476287fab42e2b3c2b57edc8ea9b0a4dcf01 Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:13:10 +0530 Subject: [PATCH 4/6] Remove config file --- .gitignore | 1 + lib/injectable.config.dart | 32 -------------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 lib/injectable.config.dart diff --git a/.gitignore b/.gitignore index 55f4b3b..97f782e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ # Generated sources *.g.dart +*.config.dart # Downloaded files mac-vendors-export.csv \ No newline at end of file diff --git a/lib/injectable.config.dart b/lib/injectable.config.dart deleted file mode 100644 index bc529b4..0000000 --- a/lib/injectable.config.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// InjectableConfigGenerator -// ************************************************************************** - -// ignore_for_file: unnecessary_lambdas -// ignore_for_file: lines_longer_than_80_chars -// coverage:ignore-file - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:get_it/get_it.dart' as _i1; -import 'package:injectable/injectable.dart' as _i2; - -import 'src/services/arp_service.dart' as _i3; -import 'src/services/impls/arp_service_sembast_impl.dart' as _i4; - -extension GetItInjectableX on _i1.GetIt { -// initializes the registration of main-scope dependencies inside of GetIt - _i1.GetIt init({ - String? environment, - _i2.EnvironmentFilter? environmentFilter, - }) { - final gh = _i2.GetItHelper( - this, - environment, - environmentFilter, - ); - gh.factory<_i3.ARPService>(() => _i4.ARPServiceSembastImpl()); - return this; - } -} From 60a918ad8b15a3c8c6d525d8ac5038a91b845733 Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:48:49 +0530 Subject: [PATCH 5/6] Adjust gitignore and pubignore for csv --- .gitignore | 2 +- .pubignore | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 97f782e..4921e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ *.config.dart # Downloaded files -mac-vendors-export.csv \ No newline at end of file +/mac-vendors-export.csv \ No newline at end of file diff --git a/.pubignore b/.pubignore index a9a6968..1266efe 100644 --- a/.pubignore +++ b/.pubignore @@ -13,4 +13,5 @@ **/test/ **/donation/ *.sh +/mac-vendors-export.csv From b718f3ba50c4250cf9b9050fbdccf69feded04ac Mon Sep 17 00:00:00 2001 From: git-elliot Date: Sun, 24 Sep 2023 18:50:17 +0530 Subject: [PATCH 6/6] ignore csv in pub --- .pubignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pubignore b/.pubignore index 1266efe..680e79b 100644 --- a/.pubignore +++ b/.pubignore @@ -13,5 +13,5 @@ **/test/ **/donation/ *.sh -/mac-vendors-export.csv +*.csv