1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'base/io.dart'; 8import 'device.dart'; 9import 'globals.dart'; 10 11/// Discovers a specific service protocol on a device, and forwards the service 12/// protocol device port to the host. 13class ProtocolDiscovery { 14 ProtocolDiscovery._( 15 this.logReader, 16 this.serviceName, { 17 this.portForwarder, 18 this.hostPort, 19 this.ipv6, 20 }) : assert(logReader != null) { 21 _deviceLogSubscription = logReader.logLines.listen(_handleLine); 22 } 23 24 factory ProtocolDiscovery.observatory( 25 DeviceLogReader logReader, { 26 DevicePortForwarder portForwarder, 27 int hostPort, 28 bool ipv6 = false, 29 }) { 30 const String kObservatoryService = 'Observatory'; 31 return ProtocolDiscovery._( 32 logReader, 33 kObservatoryService, 34 portForwarder: portForwarder, 35 hostPort: hostPort, 36 ipv6: ipv6, 37 ); 38 } 39 40 final DeviceLogReader logReader; 41 final String serviceName; 42 final DevicePortForwarder portForwarder; 43 final int hostPort; 44 final bool ipv6; 45 46 final Completer<Uri> _completer = Completer<Uri>(); 47 48 StreamSubscription<String> _deviceLogSubscription; 49 50 /// The discovered service URI. 51 Future<Uri> get uri => _completer.future; 52 53 Future<void> cancel() => _stopScrapingLogs(); 54 55 Future<void> _stopScrapingLogs() async { 56 await _deviceLogSubscription?.cancel(); 57 _deviceLogSubscription = null; 58 } 59 60 void _handleLine(String line) { 61 Uri uri; 62 final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)'); 63 final Match match = r.firstMatch(line); 64 65 if (match != null) { 66 try { 67 uri = Uri.parse(match[1]); 68 } catch (error, stackTrace) { 69 _stopScrapingLogs(); 70 _completer.completeError(error, stackTrace); 71 } 72 } 73 74 if (uri != null) { 75 assert(!_completer.isCompleted); 76 _stopScrapingLogs(); 77 _completer.complete(_forwardPort(uri)); 78 } 79 80 } 81 82 Future<Uri> _forwardPort(Uri deviceUri) async { 83 printTrace('$serviceName URL on device: $deviceUri'); 84 Uri hostUri = deviceUri; 85 86 if (portForwarder != null) { 87 final int actualDevicePort = deviceUri.port; 88 final int actualHostPort = await portForwarder.forward(actualDevicePort, hostPort: hostPort); 89 printTrace('Forwarded host port $actualHostPort to device port $actualDevicePort for $serviceName'); 90 hostUri = deviceUri.replace(port: actualHostPort); 91 } 92 93 assert(InternetAddress(hostUri.host).isLoopback); 94 if (ipv6) { 95 hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host); 96 } 97 98 return hostUri; 99 } 100} 101