Line data Source code
1 : import 'package:dart_ping/dart_ping.dart';
2 : import 'package:network_tools/network_tools.dart';
3 : import 'package:network_tools/src/injection.dart';
4 : import 'package:network_tools/src/network_tools_utils.dart';
5 : import 'package:network_tools/src/repository/repository.dart';
6 : import 'package:universal_io/io.dart';
7 :
8 : /// ActiveHost which implements comparable
9 : /// By default sort by hostId ascending
10 : class ActiveHost {
11 1 : ActiveHost({
12 : required this.internetAddress,
13 : this.openPorts = const [],
14 : String? macAddress,
15 : PingResponse? pingData,
16 : MdnsInfo? mdnsInfoVar,
17 : }) {
18 1 : _macAddress = macAddress;
19 2 : final String tempAddress = internetAddress.address;
20 :
21 1 : if (tempAddress.contains('.')) {
22 2 : hostId = tempAddress.substring(
23 2 : tempAddress.lastIndexOf('.') + 1,
24 1 : tempAddress.length,
25 : );
26 0 : } else if (tempAddress.contains(':')) {
27 0 : hostId = tempAddress.substring(
28 0 : tempAddress.lastIndexOf(':') + 1,
29 0 : tempAddress.length,
30 : );
31 : } else {
32 0 : hostId = '-1';
33 : }
34 1 : pingData ??= getPingData(tempAddress);
35 1 : _pingData = pingData;
36 :
37 2 : hostName = setHostInfo();
38 :
39 : // For some reason when internetAddress.host get called before the reverse
40 : // there is weired value
41 3 : weirdHostName = internetAddress.host;
42 :
43 : if (mdnsInfoVar != null) {
44 0 : mdnsInfo = Future.value(mdnsInfoVar);
45 : } else {
46 2 : mdnsInfo = setMdnsInfo();
47 : }
48 :
49 2 : deviceName = setDeviceName();
50 :
51 : // fetch entry from in memory arp table
52 2 : arpData = setARPData();
53 :
54 : // fetch vendor from in memory vendor table
55 2 : vendor = setVendor();
56 : }
57 1 : factory ActiveHost.buildWithAddress({
58 : required String address,
59 : String? macAddress,
60 : List<OpenPort> openPorts = const [],
61 : PingResponse? pingData,
62 : MdnsInfo? mdnsInfo,
63 : }) {
64 1 : final InternetAddress? internetAddressTemp = InternetAddress.tryParse(
65 : address,
66 : );
67 : if (internetAddressTemp == null) {
68 0 : throw 'Cant parse address $address to InternetAddress';
69 : }
70 1 : return ActiveHost(
71 : internetAddress: internetAddressTemp,
72 : macAddress: macAddress,
73 : openPorts: openPorts,
74 : pingData: pingData,
75 : mdnsInfoVar: mdnsInfo,
76 : );
77 : }
78 :
79 1 : factory ActiveHost.fromSendableActiveHost({
80 : required SendableActiveHost sendableActiveHost,
81 : String? macAddress,
82 : MdnsInfo? mdnsInfo,
83 : }) {
84 1 : final InternetAddress? internetAddressTemp = InternetAddress.tryParse(
85 1 : sendableActiveHost.address,
86 : );
87 : if (internetAddressTemp == null) {
88 0 : throw 'Cant parse address ${sendableActiveHost.address} to InternetAddress';
89 : }
90 1 : return ActiveHost(
91 : internetAddress: internetAddressTemp,
92 : macAddress: macAddress,
93 1 : openPorts: sendableActiveHost.openPorts,
94 1 : pingData: sendableActiveHost.pingData,
95 : mdnsInfoVar: mdnsInfo,
96 : );
97 : }
98 :
99 : static const generic = 'Generic Device';
100 :
101 : InternetAddress internetAddress;
102 :
103 : /// The device specific number in the ip address. In IPv4 numbers after the
104 : /// last dot, in IPv6 the numbers after the last colon
105 : late String hostId;
106 :
107 : /// Host name of the device, not to be confused with deviceName which does
108 : /// not follow any internet protocol property
109 : late Future<String?> hostName;
110 : late String weirdHostName;
111 : late final PingResponse? _pingData;
112 :
113 : /// Mdns information of this device
114 : late Future<MdnsInfo?> mdnsInfo;
115 :
116 : /// Resolve ARP data for this host.
117 : /// only supported on Linux, Macos and Windows otherwise null
118 : late Future<ARPData?> arpData;
119 :
120 : /// Only works if arpData is not null and have valid mac address.
121 : late Future<Vendor?> vendor;
122 :
123 : String? _macAddress;
124 1 : Future<String?> getMacAddress() async =>
125 2 : _macAddress ?? (await arpData)?.macAddress;
126 :
127 : /// List of all the open port of this device
128 : List<OpenPort> openPorts;
129 :
130 : /// This device name does not following any guideline and is just some name
131 : /// that we can show for the device.
132 : /// Preferably hostName, if not than mDNS name, if not than will get the
133 : /// value of [generic].
134 : /// This value **can change after the object got created** since getting
135 : /// host name of device is running async function.
136 : late Future<String> deviceName;
137 0 : PingResponse? get pingData => _pingData;
138 1 : Duration? get responseTime {
139 1 : final event = _pingData;
140 1 : if (event is PingResponse) return event.time;
141 : return null;
142 : }
143 :
144 3 : String get address => internetAddress.address;
145 :
146 1 : static PingResponse? getPingData(String host) {
147 : const int timeoutInSeconds = 1;
148 : PingResponse? tempPingData;
149 :
150 1 : Ping(
151 : host,
152 : count: 1,
153 : timeout: timeoutInSeconds,
154 1 : forceCodepage: Platform.isWindows,
155 3 : ).stream.listen((Object? event) {
156 1 : final PingResponse? response = _extractPingResponse(event);
157 1 : if (response != null && response.time != null) {
158 : tempPingData = response;
159 : }
160 : });
161 : return tempPingData;
162 : }
163 :
164 1 : static PingResponse? _extractPingResponse(Object? event) {
165 1 : if (event is PingResponse) {
166 : return event;
167 : }
168 : if (event == null) {
169 : return null;
170 : }
171 :
172 : // dart_ping changed its stream payload type across versions.
173 : // Some versions emit PingSummary or other payloads instead of a response.
174 : try {
175 : final dynamic dynamicEvent = event;
176 : // ignore: avoid_dynamic_calls
177 1 : final Object? response = dynamicEvent.response;
178 0 : return response is PingResponse ? response : null;
179 : } catch (error) {
180 : return null;
181 : }
182 : }
183 :
184 : /// Try to find the host name of this device, if not exist host name will
185 : /// stay null
186 1 : Future<String?> setHostInfo() async {
187 : // For some reason when internetAddress.host get called before the reverse
188 : // there is weired value
189 3 : weirdHostName = internetAddress.host;
190 :
191 : // In the future if mdnsInfo is null it will execute a search
192 : // Currently the functionality is missing in dart multicast_dns package
193 : // https://github.com/flutter/flutter/issues/96755
194 :
195 : try {
196 3 : internetAddress = await internetAddress.reverse();
197 2 : return internetAddress.host;
198 : } catch (e) {
199 0 : if (e is SocketException &&
200 0 : e.osError != null &&
201 0 : (e.osError!.message == 'Name or service not known')) {
202 : // Some devices does not have host name and the reverse search will just
203 : // throw exception.
204 : // We don't need to print this crash as it is by design.
205 : } else {
206 0 : logger.severe('Exception here: $e');
207 : }
208 : }
209 : return null;
210 : }
211 :
212 1 : Future<void> resolveInfo() async {
213 1 : await arpData;
214 : // await vendor;
215 1 : await deviceName;
216 1 : await mdnsInfo;
217 1 : await hostName;
218 : }
219 :
220 1 : Future<ARPData?> setARPData() {
221 4 : return getIt<Repository<ARPData>>().entryFor(address);
222 : }
223 :
224 1 : Future<Vendor?> setVendor() async {
225 1 : final String? macAddress = await getMacAddress();
226 :
227 : return macAddress == null
228 : ? null
229 0 : : await getIt<Repository<Vendor>>().entryFor(macAddress);
230 : }
231 :
232 : /// Try to find the mdns name of this device, if not exist mdns name will
233 : /// be null
234 : /// TODO: search mdns name for each device
235 1 : Future<MdnsInfo?> setMdnsInfo() async {
236 : return null;
237 : }
238 :
239 : /// Set some kind of device name.
240 : /// Will try couple of names, if all are null will just return [generic]
241 1 : Future<String> setDeviceName() async {
242 1 : final String? hostNameTemp = await hostName;
243 :
244 : if (hostNameTemp != null) {
245 : return hostNameTemp;
246 : }
247 0 : final MdnsInfo? mdnsTemp = await mdnsInfo;
248 : if (mdnsTemp != null) {
249 0 : return mdnsTemp.getOnlyTheStartOfMdnsName();
250 : }
251 : return generic;
252 : }
253 :
254 1 : @override
255 2 : int get hashCode => address.hashCode;
256 :
257 1 : @override
258 4 : bool operator ==(Object o) => o is ActiveHost && address == o.address;
259 :
260 0 : int compareTo(ActiveHost other) {
261 0 : return hostId.compareTo(other.hostId);
262 : }
263 :
264 1 : @override
265 : String toString() {
266 6 : return 'Address: $address, HostId: $hostId, Time: ${responseTime?.inMilliseconds}ms, port: ${openPorts.join(",")}';
267 : }
268 :
269 0 : Future<String> toStringFull() async {
270 0 : return 'Address: $address, MAC: ${(await arpData)?.macAddress}, HostId: $hostId, Vendor: ${(await vendor)?.vendorName} Time: ${responseTime?.inMilliseconds}ms, DeviceName: ${await deviceName}, HostName: ${await hostName}, MdnsInfo: ${await mdnsInfo}';
271 : }
272 : }
|