LCOV - code coverage report
Current view: top level - lib/src/device_info - arp_table_helper.dart (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 88.9 % 36 32
Test Date: 2025-08-17 13:02:53 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:convert';
       2              : 
       3              : import 'package:logging/logging.dart';
       4              : import 'package:network_tools/src/models/arp_data.dart';
       5              : import 'package:universal_io/io.dart';
       6              : 
       7              : /// Retreiving ARP packets is only supported for Desktop such as
       8              : /// Linux, Windows, and macOS. Dart native doesn't provide a way or rejects
       9              : /// call to arp command on mobile such as Android and iOS.
      10              : /// Maybe in future dart native will support sending raw packets,
      11              : /// so that time we can add implementation for mobile devices.
      12              : /// Currenlty this is achieved by process package.
      13              : /// Helper class for retrieving and parsing the ARP table on supported desktop platforms.
      14              : class ARPTableHelper {
      15              :   /// Logger for ARP table operations.
      16            9 :   static final arpLogger = Logger("arp-table-logger");
      17              : 
      18              :   bool isMobilePlatform = Platform.isAndroid || Platform.isIOS;
      19              :   bool isMacOSPlatform = Platform.isMacOS;
      20              :   bool isLinuxPlatform = Platform.isLinux;
      21              : 
      22            3 :   List<String> executeARPCommand() {
      23              :     // ARP is not allowed to be run for mobile devices currenlty.
      24            3 :     if (isMobilePlatform) {
      25            0 :       arpLogger.warning("ARP command is not supported on mobile platforms.");
      26            0 :       return [];
      27              :     }
      28            6 :     final result = Process.runSync('arp', ['-a']);
      29            6 :     if (result.exitCode != 0) {
      30            0 :       arpLogger.severe("Failed to execute ARP command: ${result.stderr}");
      31            0 :       return [];
      32              :     }
      33            9 :     return const LineSplitter().convert(result.stdout.toString());
      34              :   }
      35              : 
      36            3 :   RegExp _getARPCmdRegExPattern() {
      37            3 :     if (isMacOSPlatform) {
      38            1 :       return RegExp(
      39              :         r'(?<host>[\w.?]*)\s\((?<ip>.*)\)\sat\s(?<mac>.*)\son\s(?<intf>\w+)\sifscope\s*(\w*)\s*\[(?<typ>.*)\]',
      40              :       );
      41            3 :     } else if (isLinuxPlatform) {
      42            3 :       return RegExp(
      43              :         r'(?<host>[\w.?]*)\s\((?<ip>.*)\)\sat\s(?<mac>.*)\s\[(?<typ>.*)\]\son\s(?<intf>\w+)',
      44              :       );
      45              :     } else {
      46              :       // Windows: non-greedy match and trim whitespace
      47            1 :       return RegExp(
      48              :         r'(?<ip>[^\s]+)\s+(?<mac>[^\s]+)\s+(?<typ>\w+)',
      49              :         caseSensitive: false,
      50              :       );
      51              :     }
      52              :   }
      53              : 
      54              :   /// Retrieves the ARP table by running the `arp -a` command on Linux, Windows, and macOS.
      55              :   ///
      56              :   /// Parses the output and returns a list of [ARPData] objects representing each ARP entry.
      57              :   /// Returns an empty list on unsupported platforms (e.g., Android, iOS).
      58            3 :   Future<List<ARPData>> buildTable() async {
      59            3 :     final Map<String, ARPData> arpEntries = {};
      60            6 :     final int startTime = DateTime.now().millisecondsSinceEpoch;
      61            3 :     final entries = executeARPCommand();
      62            3 :     if (entries.isEmpty) {
      63            2 :       arpLogger.warning("No ARP entries found.");
      64            1 :       return [];
      65              :     }
      66              : 
      67            3 :     final pattern = _getARPCmdRegExPattern();
      68              : 
      69            6 :     for (final entry in entries) {
      70              :       // Skip Windows header and interface lines
      71            6 :       if (entry.trim().isEmpty ||
      72            3 :           entry.startsWith('Interface:') ||
      73            9 :           entry.trim().toLowerCase().startsWith('internet address')) {
      74              :         continue;
      75              :       }
      76            3 :       final match = pattern.firstMatch(entry);
      77              :       if (match != null) {
      78            3 :         final arpData = ARPData.fromRegExpMatch(match);
      79            6 :         if (arpData.macAddress != '(incomplete)') {
      80            9 :           arpLogger.fine("Adding entry to table -> $arpData");
      81            6 :           arpEntries[arpData.iPAddress] = arpData;
      82              :         }
      83              :       }
      84              :     }
      85            6 :     arpLogger.fine(
      86           15 :       "ARP calculation took ${DateTime.now().millisecondsSinceEpoch - startTime} ms with ${arpEntries.length} entries",
      87              :     );
      88            6 :     return arpEntries.values.toList();
      89              :   }
      90              : }
        

Generated by: LCOV version 2.0-1