LCOV - code coverage report
Current view: top level - lib/src/services/impls - mdns_scanner_service_impl.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 57.4 % 101 58
Test Date: 2026-07-04 10:50:15 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : import 'dart:typed_data';
       3              : import 'package:multicast_dns/multicast_dns.dart';
       4              : import 'package:network_tools/network_tools.dart';
       5              : import 'package:network_tools/src/mdns_scanner/get_srv_list_by_os/srv_list.dart';
       6              : import 'package:network_tools/src/network_tools_utils.dart';
       7              : import 'package:universal_io/io.dart';
       8              : 
       9              : /// Converts a network address coming from either the Dart IO or universal_io
      10              : /// implementation into the package's [InternetAddress] type.
      11            1 : InternetAddress normalizeInternetAddress(dynamic address) {
      12            1 :   if (address is InternetAddress) {
      13              :     return address;
      14              :   }
      15              : 
      16            1 :   if (address is String) {
      17            1 :     final parsedAddress = InternetAddress.tryParse(address);
      18              :     if (parsedAddress != null) {
      19              :       return parsedAddress;
      20              :     }
      21              :   }
      22              : 
      23            1 :   if (address is Object) {
      24              :     try {
      25            1 :       final dynamic addressValue = (address as dynamic).address;
      26            1 :       if (addressValue is String) {
      27            1 :         final parsedAddress = InternetAddress.tryParse(addressValue);
      28              :         if (parsedAddress != null) {
      29              :           return parsedAddress;
      30              :         }
      31              :       }
      32              :     } catch (_) {}
      33              : 
      34              :     try {
      35            1 :       final dynamic rawAddress = (address as dynamic).rawAddress;
      36            1 :       if (rawAddress is List<int>) {
      37            2 :         return InternetAddress.fromRawAddress(Uint8List.fromList(rawAddress));
      38              :       }
      39              :     } catch (_) {}
      40              :   }
      41              : 
      42            1 :   throw ArgumentError.value(
      43              :     address,
      44              :     'address',
      45              :     'Unsupported InternetAddress implementation',
      46              :   );
      47              : }
      48              : 
      49              : /// Resolves the bind target for [RawDatagramSocket.bind].
      50              : ///
      51              : /// On Windows, binding with an [InternetAddress] whose hostname is empty fails
      52              : /// with errno 10049. Always use the numeric address string instead.
      53            1 : dynamic _mdnsDatagramBindHost(dynamic host) {
      54            1 :   if (host is InternetAddress) {
      55            0 :     return host.address;
      56              :   }
      57              : 
      58              :   try {
      59            2 :     return normalizeInternetAddress(host).address;
      60              :   } catch (_) {
      61              :     return host;
      62              :   }
      63              : }
      64              : 
      65            1 : Future<RawDatagramSocket> _mdnsRawDatagramSocketFactory(
      66              :   dynamic host,
      67              :   int port, {
      68              :   bool? reuseAddress,
      69              :   bool? reusePort,
      70              :   int? ttl,
      71              : }) {
      72            1 :   return RawDatagramSocket.bind(
      73            1 :     _mdnsDatagramBindHost(host),
      74              :     port,
      75              :     reuseAddress: reuseAddress ?? true,
      76              :     reusePort:
      77            2 :         !(Platform.isWindows || Platform.isAndroid) && (reusePort ?? true),
      78              :     ttl: ttl ?? 255,
      79              :   );
      80              : }
      81              : 
      82            1 : Iterable<NetworkInterface> filterMdnsInterfaces(
      83              :   Iterable<NetworkInterface> interfaces,
      84              :   InternetAddressType type, {
      85              :   bool isWindows = false,
      86              : }) {
      87              :   return interfaces
      88            2 :       .where((interface) {
      89            3 :         final addresses = interface.addresses.where((address) {
      90            2 :           if (address.type != type) {
      91              :             return false;
      92              :           }
      93            1 :           if (address.isLoopback) {
      94              :             return false;
      95              :           }
      96            1 :           if (isWindows && address.isLinkLocal) {
      97              :             return false;
      98              :           }
      99              :           return true;
     100              :         });
     101              : 
     102            1 :         return addresses.isNotEmpty;
     103              :       })
     104            1 :       .toList(growable: false);
     105              : }
     106              : 
     107            1 : Future<Iterable<NetworkInterface>> _mdnsNetworkInterfacesFactory(
     108              :   InternetAddressType type,
     109              : ) async {
     110            1 :   final interfaces = await NetworkInterface.list(
     111            1 :     includeLinkLocal: !Platform.isWindows,
     112              :     type: type,
     113              :   );
     114            2 :   return filterMdnsInterfaces(interfaces, type, isWindows: Platform.isWindows);
     115              : }
     116              : 
     117              : class MdnsScannerServiceImpl extends MdnsScannerService {
     118              :   /// This method searching for all the mdns devices in the network.
     119              :   /// TODO: The implementation is **Lacking!** and will not find all the
     120              :   /// TODO: results that actual exist in the network!, only some of them.
     121              :   /// TODO: This is because missing functionality in dart
     122              :   /// TODO: https://github.com/flutter/flutter/issues/97210
     123              :   /// TODO: In some cases we resolve this missing functionality using
     124              :   /// TODO: specific os tools.
     125              : 
     126            1 :   @override
     127              :   Future<List<ActiveHost>> searchMdnsDevices({
     128              :     bool forceUseOfSavedSrvRecordList = false,
     129              :   }) async {
     130              :     List<String> srvRecordListToSearchIn;
     131              : 
     132              :     if (forceUseOfSavedSrvRecordList) {
     133            1 :       srvRecordListToSearchIn = tcpSrvRecordsList;
     134            2 :       srvRecordListToSearchIn.addAll(udpSrvRecordsList);
     135              :     } else {
     136            1 :       final List<String>? srvRecordsFromOs = await SrvList.getSrvRecordList();
     137              : 
     138            1 :       if (srvRecordsFromOs == null || srvRecordsFromOs.isEmpty) {
     139            1 :         srvRecordListToSearchIn = tcpSrvRecordsList;
     140            2 :         srvRecordListToSearchIn.addAll(udpSrvRecordsList);
     141              :       } else {
     142              :         srvRecordListToSearchIn = srvRecordsFromOs;
     143              :       }
     144              :     }
     145              : 
     146            1 :     final List<Future<List<ActiveHost>>> activeHostListsFuture = [];
     147            2 :     for (final String srvRecord in srvRecordListToSearchIn) {
     148            2 :       activeHostListsFuture.add(findingMdnsWithAddress(srvRecord));
     149              :     }
     150              : 
     151            1 :     final List<ActiveHost> activeHostList = [];
     152              : 
     153              :     for (final Future<List<ActiveHost>> activeHostListFuture
     154            2 :         in activeHostListsFuture) {
     155            1 :       activeHostList.addAll(await activeHostListFuture);
     156              :     }
     157              : 
     158              :     return activeHostList;
     159              :   }
     160              : 
     161            1 :   @override
     162              :   Future<List<ActiveHost>> findingMdnsWithAddress(String serviceType) async {
     163            1 :     final MDnsClient client = MDnsClient(
     164              :       rawDatagramSocketFactory: _mdnsRawDatagramSocketFactory,
     165              :     );
     166              : 
     167            1 :     final List<ActiveHost> listOfActiveHost = [];
     168            1 :     final Completer<void> completer = Completer<void>();
     169              : 
     170            1 :     runZonedGuarded(
     171            1 :       () async {
     172              :         try {
     173            1 :           await client.start(
     174            1 :             listenAddress: InternetAddress.anyIPv4,
     175              :             interfacesFactory: _mdnsNetworkInterfacesFactory,
     176              :           );
     177              : 
     178            1 :           await for (final PtrResourceRecord ptr
     179            1 :               in client.lookup<PtrResourceRecord>(
     180            1 :                 ResourceRecordQuery.serverPointer(serviceType),
     181            0 :               )) {
     182            0 :             await for (final SrvResourceRecord srv
     183            0 :                 in client.lookup<SrvResourceRecord>(
     184            0 :                   ResourceRecordQuery.service(ptr.domainName),
     185            0 :                 )) {
     186            0 :               await for (final TxtResourceRecord txtRecords
     187            0 :                   in client.lookup<TxtResourceRecord>(
     188            0 :                     ResourceRecordQuery.text(ptr.domainName),
     189            0 :                   )) {
     190            0 :                 listOfActiveHost.addAll(
     191            0 :                   await findAllActiveHostForSrv(
     192            0 :                     addressType: InternetAddress.anyIPv4,
     193              :                     client: client,
     194              :                     ptr: ptr,
     195              :                     srv: srv,
     196              :                     txt: txtRecords,
     197              :                   ),
     198              :                 );
     199            0 :                 listOfActiveHost.addAll(
     200            0 :                   await findAllActiveHostForSrv(
     201            0 :                     addressType: InternetAddress.anyIPv6,
     202              :                     client: client,
     203              :                     ptr: ptr,
     204              :                     srv: srv,
     205              :                     txt: txtRecords,
     206              :                   ),
     207              :                 );
     208              :               }
     209              :             }
     210              :           }
     211              :         } catch (e) {
     212            0 :           logger.severe(
     213            0 :             'Error finding mdns devices for serviceType $serviceType: $e',
     214              :           );
     215              :         } finally {
     216            1 :           client.stop();
     217            2 :           if (!completer.isCompleted) completer.complete();
     218              :         }
     219              :       },
     220            0 :       (Object error, StackTrace stack) {
     221            0 :         logger.severe(
     222            0 :           'Unhandled async error in findingMdnsWithAddress for $serviceType: $error',
     223              :         );
     224            0 :         client.stop();
     225            0 :         if (!completer.isCompleted) completer.complete();
     226              :       },
     227              :     );
     228              : 
     229            1 :     await completer.future;
     230              :     return listOfActiveHost;
     231              :   }
     232              : 
     233            0 :   @override
     234              :   Future<List<ActiveHost>> findAllActiveHostForSrv({
     235              :     required InternetAddress addressType,
     236              :     required MDnsClient client,
     237              :     required PtrResourceRecord ptr,
     238              :     required SrvResourceRecord srv,
     239              :     required TxtResourceRecord txt,
     240              :   }) async {
     241            0 :     final List<ActiveHost> listOfActiveHost = [];
     242              :     try {
     243              :       Stream<IPAddressResourceRecord> iPAddressResourceRecordStream;
     244              : 
     245            0 :       if (addressType == InternetAddress.anyIPv4) {
     246            0 :         iPAddressResourceRecordStream = client.lookup<IPAddressResourceRecord>(
     247            0 :           ResourceRecordQuery.addressIPv4(srv.target),
     248              :         );
     249              :       } else {
     250            0 :         iPAddressResourceRecordStream = client.lookup<IPAddressResourceRecord>(
     251            0 :           ResourceRecordQuery.addressIPv6(srv.target),
     252              :         );
     253              :       }
     254            0 :       await for (final IPAddressResourceRecord ip
     255            0 :           in iPAddressResourceRecordStream) {
     256            0 :         final ActiveHost activeHost = convertSrvToHostName(
     257            0 :           internetAddress: normalizeInternetAddress(ip.address),
     258              :           ptr: ptr,
     259              :           srv: srv,
     260              :           txt: txt,
     261              :         );
     262              : 
     263            0 :         listOfActiveHost.add(activeHost);
     264              :       }
     265              :     } catch (e) {
     266            0 :       logger.severe(
     267            0 :         'Error finding ip of mdns record ${ptr.name} srv target ${srv.target}, will add it with ip 0.0.0.0\n$e',
     268              :       );
     269            0 :       final ActiveHost activeHost = convertSrvToHostName(
     270            0 :         internetAddress: InternetAddress('0.0.0.0'),
     271              :         srv: srv,
     272              :         ptr: ptr,
     273              :         txt: txt,
     274              :       );
     275            0 :       listOfActiveHost.add(activeHost);
     276              :     }
     277              :     return listOfActiveHost;
     278              :   }
     279              : 
     280            0 :   @override
     281              :   ActiveHost convertSrvToHostName({
     282              :     required InternetAddress internetAddress,
     283              :     required PtrResourceRecord ptr,
     284              :     required SrvResourceRecord srv,
     285              :     required TxtResourceRecord txt,
     286              :   }) {
     287            0 :     final MdnsInfo mdnsInfo = MdnsInfo(
     288              :       srvResourceRecord: srv,
     289              :       ptrResourceRecord: ptr,
     290              :       txtResourceRecord: txt,
     291              :     );
     292            0 :     return ActiveHost(internetAddress: internetAddress, mdnsInfoVar: mdnsInfo);
     293              :   }
     294              : }
        

Generated by: LCOV version 2.0-1