OLD | NEW |
1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
4 | 4 |
5 library mdns.src.native_protocol_client; | 5 library mdns.src.native_protocol_client; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
11 import 'package:mdns/mdns.dart'; | 11 import 'package:mdns/mdns.dart'; |
12 import 'package:mdns/src/constants.dart'; | 12 import 'package:mdns/src/constants.dart'; |
13 import 'package:mdns/src/lookup_resolver.dart'; | 13 import 'package:mdns/src/lookup_resolver.dart'; |
14 import 'package:mdns/src/packet.dart'; | 14 import 'package:mdns/src/packet.dart'; |
15 | 15 |
| 16 /// Cache for resource records that have been received. |
| 17 /// |
| 18 /// There can be multiple entries for the same name and type. |
| 19 /// |
| 20 /// The cached is updated with a list of records, because it needs to remove |
| 21 /// all entries that correspond to name and type of the name/type combinations |
| 22 /// of records that should be updated. For example, a host may remove one |
| 23 /// of its IP addresses and report the remaining address as a response - then |
| 24 /// we need to clear all previous entries for that host before updating the |
| 25 /// cache. |
| 26 class ResourceRecordCache { |
| 27 final List buffer; |
| 28 final int size; |
| 29 int position; |
| 30 |
| 31 ResourceRecordCache({int size: 32}) |
| 32 : buffer = new List(size), |
| 33 size = size, |
| 34 position = 0; |
| 35 |
| 36 void updateRecords(List<ResourceRecord> records) { |
| 37 // TODO(karlklose): include flush bit in the record and only flush if |
| 38 // necessary. |
| 39 // Clear the cache for all name/type combinations to be updated. |
| 40 for (int i = 0; i < size; i++) { |
| 41 ResourceRecord r = buffer[i % size]; |
| 42 if (r == null) continue; |
| 43 String name = r.name; |
| 44 int type = r.type; |
| 45 for (ResourceRecord record in records) { |
| 46 if (name == record.name && type == record.type) { |
| 47 buffer[i % size] = null; |
| 48 break; |
| 49 } |
| 50 } |
| 51 } |
| 52 // Add the new records. |
| 53 for (ResourceRecord record in records) { |
| 54 buffer[position] = record; |
| 55 position = (position + 1) % size; |
| 56 } |
| 57 } |
| 58 |
| 59 void lookup(String name, int type, List results) { |
| 60 int time = new DateTime.now().millisecondsSinceEpoch; |
| 61 for (int i = position + size; i >= position; i--) { |
| 62 int index = i % size; |
| 63 ResourceRecord record = buffer[index]; |
| 64 if (record == null) continue; |
| 65 if (record.validUntil < time) { |
| 66 buffer[index] = null; |
| 67 } else if (record.name == name && record.type == type) { |
| 68 results.add(record); |
| 69 } |
| 70 } |
| 71 } |
| 72 } |
| 73 |
16 // Implementation of mDNS client using the native protocol. | 74 // Implementation of mDNS client using the native protocol. |
17 class NativeProtocolMDnsClient implements MDnsClient { | 75 class NativeProtocolMDnsClient implements MDnsClient { |
18 bool _starting = false; | 76 bool _starting = false; |
19 bool _started = false; | 77 bool _started = false; |
20 RawDatagramSocket _incoming; | 78 RawDatagramSocket _incoming; |
21 final List<RawDatagramSocket> _sockets = <RawDatagramSocket>[]; | 79 final List<RawDatagramSocket> _sockets = <RawDatagramSocket>[]; |
22 final LookupResolver _resolver = new LookupResolver(); | 80 final LookupResolver _resolver = new LookupResolver(); |
| 81 ResourceRecordCache cache = new ResourceRecordCache(); |
23 | 82 |
24 /// Start the mDNS client. | 83 /// Start the mDNS client. |
25 Future start() async { | 84 Future start() async { |
26 if (_started && _starting) { | 85 if (_started && _starting) { |
27 throw new StateError('mDNS client already started'); | 86 throw new StateError('mDNS client already started'); |
28 } | 87 } |
29 _starting = true; | 88 _starting = true; |
30 | 89 |
31 // Listen on all addresses. | 90 // Listen on all addresses. |
32 _incoming = await RawDatagramSocket.bind( | 91 _incoming = await RawDatagramSocket.bind( |
(...skipping 19 matching lines...) Expand all Loading... |
52 | 111 |
53 void stop() { | 112 void stop() { |
54 if (!_started) return; | 113 if (!_started) return; |
55 if (_starting) { | 114 if (_starting) { |
56 throw new StateError('Cannot stop mDNS client wile it is starting'); | 115 throw new StateError('Cannot stop mDNS client wile it is starting'); |
57 } | 116 } |
58 | 117 |
59 _sockets.forEach((socket) => socket.close()); | 118 _sockets.forEach((socket) => socket.close()); |
60 _incoming.close(); | 119 _incoming.close(); |
61 | 120 |
| 121 _resolver.clearPendingRequests(); |
| 122 |
62 _started = false; | 123 _started = false; |
63 } | 124 } |
64 | 125 |
65 Future<InternetAddress> lookup( | 126 Stream<ResourceRecord> lookup( |
66 String hostname, {Duration timeout: const Duration(seconds: 5)}) { | 127 int type, |
| 128 String name, |
| 129 {Duration timeout: const Duration(seconds: 5)}) { |
67 if (!_started) { | 130 if (!_started) { |
68 throw new StateError('mDNS client is not started'); | 131 throw new StateError('mDNS client is not started'); |
69 } | 132 } |
70 | 133 |
| 134 // Look for entries in the cache. |
| 135 List<ResourceRecordCache> cached = <ResourceRecord>[]; |
| 136 cache.lookup(name, type, cached); |
| 137 if (cached.isNotEmpty) { |
| 138 StreamController controller = new StreamController(); |
| 139 cached.forEach(controller.add); |
| 140 controller.close(); |
| 141 return controller.stream; |
| 142 } |
| 143 |
71 // Add the pending request before sending the query. | 144 // Add the pending request before sending the query. |
72 var future = _resolver.addPendingRequest(hostname, timeout); | 145 var results = _resolver.addPendingRequest(type, name, timeout); |
73 | 146 |
74 // Send the request on all interfaces. | 147 // Send the request on all interfaces. |
75 List<int> packet = encodeMDnsQuery(hostname); | 148 List<int> packet = encodeMDnsQuery(name, type); |
76 for (int i = 0; i < _sockets.length; i++) { | 149 for (int i = 0; i < _sockets.length; i++) { |
77 _sockets[i].send(packet, mDnsAddress, mDnsPort); | 150 _sockets[i].send(packet, mDnsAddress, mDnsPort); |
78 } | 151 } |
79 | 152 |
80 return future; | 153 return results; |
81 } | 154 } |
82 | 155 |
83 // Process incoming datagrams. | 156 // Process incoming datagrams. |
84 _handleIncoming(event) { | 157 _handleIncoming(event) { |
85 if (event == RawSocketEvent.READ) { | 158 if (event == RawSocketEvent.READ) { |
86 var data = _incoming.receive(); | 159 Datagram datagram = _incoming.receive(); |
87 var response = decodeMDnsResponse(data.data); | 160 List<ResourceRecord> response = decodeMDnsResponse(datagram.data); |
88 if (response != null) { | 161 if (response != null) { |
| 162 cache.updateRecords(response); |
89 _resolver.handleResponse(response); | 163 _resolver.handleResponse(response); |
90 } | 164 } |
91 } | 165 } |
92 } | 166 } |
93 } | 167 } |
OLD | NEW |