| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| |
| import 'resource_record.dart'; |
| |
| /// Class for maintaining state about pending mDNS requests. |
| class PendingRequest extends LinkedListEntry<PendingRequest> { |
| /// Creates a new PendingRequest. |
| PendingRequest(this.type, this.domainName, this.controller); |
| |
| /// The [ResourceRecordType] of the request. |
| final int type; |
| |
| /// The domain name to look up via mDNS. |
| /// |
| /// For example, `'_http._tcp.local` to look up HTTP services on the local |
| /// domain. |
| final String domainName; |
| |
| /// A StreamController managing the request. |
| final StreamController<ResourceRecord> controller; |
| |
| /// The timer for the request. |
| Timer? timer; |
| } |
| |
| /// Class for keeping track of pending lookups and processing incoming |
| /// query responses. |
| class LookupResolver { |
| final LinkedList<PendingRequest> _pendingRequests = |
| LinkedList<PendingRequest>(); |
| |
| /// Adds a request and returns a [Stream] of [ResourceRecord] responses. |
| Stream<T> addPendingRequest<T extends ResourceRecord>( |
| int type, String name, Duration timeout) { |
| final StreamController<T> controller = StreamController<T>(); |
| final PendingRequest request = PendingRequest(type, name, controller); |
| final Timer timer = Timer(timeout, () { |
| request.unlink(); |
| controller.close(); |
| }); |
| request.timer = timer; |
| _pendingRequests.add(request); |
| return controller.stream; |
| } |
| |
| /// Parses [ResoureRecord]s received and delivers them to the appropriate |
| /// listener(s) added via [addPendingRequest]. |
| void handleResponse(List<ResourceRecord> response) { |
| for (final ResourceRecord r in response) { |
| final int type = r.resourceRecordType; |
| String name = r.name.toLowerCase(); |
| if (name.endsWith('.')) { |
| name = name.substring(0, name.length - 1); |
| } |
| |
| bool responseMatches(PendingRequest request) { |
| String requestName = request.domainName.toLowerCase(); |
| // make, e.g. "_http" become "_http._tcp.local". |
| if (!requestName.endsWith('local')) { |
| if (!requestName.endsWith('._tcp.local') && |
| !requestName.endsWith('._udp.local') && |
| !requestName.endsWith('._tcp') && |
| !requestName.endsWith('.udp')) { |
| requestName += '._tcp'; |
| } |
| requestName += '.local'; |
| } |
| return requestName == name && request.type == type; |
| } |
| |
| for (final PendingRequest pendingRequest in _pendingRequests) { |
| if (responseMatches(pendingRequest)) { |
| if (pendingRequest.controller.isClosed) { |
| return; |
| } |
| pendingRequest.controller.add(r); |
| } |
| } |
| } |
| } |
| |
| /// Removes any pending requests and ends processing. |
| void clearPendingRequests() { |
| while (_pendingRequests.isNotEmpty) { |
| final PendingRequest request = _pendingRequests.first; |
| request.unlink(); |
| request.timer?.cancel(); |
| request.controller.close(); |
| } |
| } |
| } |