blob: a94000db23d6231824d5224fc2814bd2c1b70de0 [file] [log] [blame]
John McCutchan9cb70012016-03-04 15:01:26 -08001// 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
Jonah Williamse3cb2c32019-11-13 16:02:46 -08007import 'package:meta/meta.dart';
8
Todd Volkerte792c6b2017-11-21 20:12:21 -08009import 'base/io.dart';
John McCutchan9cb70012016-03-04 15:01:26 -080010import 'device.dart';
Dan Rubel93e662a2016-12-06 09:19:12 -080011import 'globals.dart';
John McCutchan9cb70012016-03-04 15:01:26 -080012
Ian Hickson73dcca62017-05-16 11:38:07 -070013/// Discovers a specific service protocol on a device, and forwards the service
Todd Volkert10decc72017-05-16 08:25:51 -070014/// protocol device port to the host.
Devon Carew40c0d6e2016-05-12 18:15:23 -070015class ProtocolDiscovery {
Todd Volkert10decc72017-05-16 08:25:51 -070016 ProtocolDiscovery._(
Ian Hickson73dcca62017-05-16 11:38:07 -070017 this.logReader,
18 this.serviceName, {
Todd Volkert10decc72017-05-16 08:25:51 -070019 this.portForwarder,
Emmanuel Garcia6d779962019-11-19 15:11:41 -080020 this.throttleDuration,
Todd Volkert10decc72017-05-16 08:25:51 -070021 this.hostPort,
Jonah Williamse3cb2c32019-11-13 16:02:46 -080022 this.devicePort,
Todd Volkerte792c6b2017-11-21 20:12:21 -080023 this.ipv6,
Emmanuel Garcia6d779962019-11-19 15:11:41 -080024 }) : assert(logReader != null)
25 {
26 _deviceLogSubscription = logReader.logLines.listen(
27 _handleLine,
28 onDone: _stopScrapingLogs,
29 );
30 _uriStreamController = StreamController<Uri>.broadcast();
Devon Carewb0dca792016-04-27 14:43:42 -070031 }
32
Ian Hickson73dcca62017-05-16 11:38:07 -070033 factory ProtocolDiscovery.observatory(
34 DeviceLogReader logReader, {
35 DevicePortForwarder portForwarder,
Emmanuel Garcia6d779962019-11-19 15:11:41 -080036 Duration throttleDuration = const Duration(milliseconds: 200),
Jonah Williamse3cb2c32019-11-13 16:02:46 -080037 @required int hostPort,
38 @required int devicePort,
39 @required bool ipv6,
Ian Hickson73dcca62017-05-16 11:38:07 -070040 }) {
41 const String kObservatoryService = 'Observatory';
Alexandre Ardhuind927c932018-09-12 08:29:29 +020042 return ProtocolDiscovery._(
Matteo Crippa8c02b8f2018-08-04 21:06:56 +020043 logReader,
44 kObservatoryService,
Ian Hickson73dcca62017-05-16 11:38:07 -070045 portForwarder: portForwarder,
Emmanuel Garcia6d779962019-11-19 15:11:41 -080046 throttleDuration: throttleDuration,
Ian Hickson73dcca62017-05-16 11:38:07 -070047 hostPort: hostPort,
Jonah Williamse3cb2c32019-11-13 16:02:46 -080048 devicePort: devicePort,
Todd Volkerte792c6b2017-11-21 20:12:21 -080049 ipv6: ipv6,
Ian Hickson73dcca62017-05-16 11:38:07 -070050 );
51 }
Dan Rubel93e662a2016-12-06 09:19:12 -080052
Ian Hickson73dcca62017-05-16 11:38:07 -070053 final DeviceLogReader logReader;
54 final String serviceName;
Dan Rubel93e662a2016-12-06 09:19:12 -080055 final DevicePortForwarder portForwarder;
Todd Volkert10decc72017-05-16 08:25:51 -070056 final int hostPort;
Jonah Williamse3cb2c32019-11-13 16:02:46 -080057 final int devicePort;
Todd Volkerte792c6b2017-11-21 20:12:21 -080058 final bool ipv6;
Ian Hickson73dcca62017-05-16 11:38:07 -070059
Emmanuel Garcia6d779962019-11-19 15:11:41 -080060 /// The time to wait before forwarding a new observatory URIs from [logReader].
61 final Duration throttleDuration;
Devon Carewb0dca792016-04-27 14:43:42 -070062
Todd Volkert10decc72017-05-16 08:25:51 -070063 StreamSubscription<String> _deviceLogSubscription;
Emmanuel Garcia6d779962019-11-19 15:11:41 -080064 StreamController<Uri> _uriStreamController;
John McCutchan9cb70012016-03-04 15:01:26 -080065
Todd Volkert10decc72017-05-16 08:25:51 -070066 /// The discovered service URI.
Emmanuel Garcia6d779962019-11-19 15:11:41 -080067 /// Use [uris] instead.
68 // TODO(egarciad): replace `uri` for `uris`.
69 Future<Uri> get uri {
70 return uris.first;
71 }
72
73 /// The discovered service URIs.
Christopher Fujinoed482c32019-10-09 16:30:27 -070074 ///
Emmanuel Garcia6d779962019-11-19 15:11:41 -080075 /// When a new observatory URI is available in [logReader],
76 /// the URIs are forwarded at most once every [throttleDuration].
77 ///
78 /// Port forwarding is only attempted when this is invoked,
79 /// for each observatory URI in the stream.
80 Stream<Uri> get uris {
81 return _uriStreamController.stream
82 .transform(_throttle<Uri>(
83 waitDuration: throttleDuration,
84 ))
85 .asyncMap<Uri>(_forwardPort);
Christopher Fujinoed482c32019-10-09 16:30:27 -070086 }
John McCutchan9cb70012016-03-04 15:01:26 -080087
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +020088 Future<void> cancel() => _stopScrapingLogs();
Todd Volkert10decc72017-05-16 08:25:51 -070089
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +020090 Future<void> _stopScrapingLogs() async {
Emmanuel Garcia6d779962019-11-19 15:11:41 -080091 await _uriStreamController?.close();
Todd Volkert10decc72017-05-16 08:25:51 -070092 await _deviceLogSubscription?.cancel();
93 _deviceLogSubscription = null;
Devon Carewb0dca792016-04-27 14:43:42 -070094 }
95
Emmanuel Garcia6d779962019-11-19 15:11:41 -080096 Match _getPatternMatch(String line) {
97 final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)');
98 return r.firstMatch(line);
99 }
100
101 Uri _getObservatoryUri(String line) {
102 final Match match = _getPatternMatch(line);
103 if (match != null) {
104 return Uri.parse(match[1]);
105 }
106 return null;
107 }
108
Ian Hickson73dcca62017-05-16 11:38:07 -0700109 void _handleLine(String line) {
Dan Rubela9584e12016-11-30 20:29:04 -0500110 Uri uri;
Emmanuel Garcia6d779962019-11-19 15:11:41 -0800111 try {
112 uri = _getObservatoryUri(line);
113 } on FormatException catch(error, stackTrace) {
114 _uriStreamController.addError(error, stackTrace);
John McCutchan9cb70012016-03-04 15:01:26 -0800115 }
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800116 if (uri == null) {
117 return;
Jonah Williams985da832019-11-13 14:35:07 -0800118 }
Emmanuel Garcia6d779962019-11-19 15:11:41 -0800119 if (devicePort != null && uri.port != devicePort) {
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800120 printTrace('skipping potential observatory $uri due to device port mismatch');
121 return;
122 }
Emmanuel Garcia6d779962019-11-19 15:11:41 -0800123 _uriStreamController.add(uri);
John McCutchan9cb70012016-03-04 15:01:26 -0800124 }
125
Todd Volkert10decc72017-05-16 08:25:51 -0700126 Future<Uri> _forwardPort(Uri deviceUri) async {
Ian Hickson73dcca62017-05-16 11:38:07 -0700127 printTrace('$serviceName URL on device: $deviceUri');
Todd Volkert10decc72017-05-16 08:25:51 -0700128 Uri hostUri = deviceUri;
Devon Carewb0dca792016-04-27 14:43:42 -0700129
Todd Volkert10decc72017-05-16 08:25:51 -0700130 if (portForwarder != null) {
Ian Hickson35ad2a72018-06-27 16:44:28 -0700131 final int actualDevicePort = deviceUri.port;
132 final int actualHostPort = await portForwarder.forward(actualDevicePort, hostPort: hostPort);
133 printTrace('Forwarded host port $actualHostPort to device port $actualDevicePort for $serviceName');
134 hostUri = deviceUri.replace(port: actualHostPort);
Todd Volkert10decc72017-05-16 08:25:51 -0700135 }
136
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200137 assert(InternetAddress(hostUri.host).isLoopback);
Todd Volkerte792c6b2017-11-21 20:12:21 -0800138 if (ipv6) {
Ian Hickson35ad2a72018-06-27 16:44:28 -0700139 hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host);
Todd Volkerte792c6b2017-11-21 20:12:21 -0800140 }
Todd Volkert10decc72017-05-16 08:25:51 -0700141 return hostUri;
John McCutchan9cb70012016-03-04 15:01:26 -0800142 }
143}
Emmanuel Garcia6d779962019-11-19 15:11:41 -0800144
145/// This transformer will produce an event at most once every [waitDuration].
146///
147/// For example, consider a `waitDuration` of `10ms`, and list of event names
148/// and arrival times: `a (0ms), b (5ms), c (11ms), d (21ms)`.
149/// The events `c` and `d` will be produced as a result.
150StreamTransformer<S, S> _throttle<S>({
151 @required Duration waitDuration,
152}) {
153 assert(waitDuration != null);
154
155 S latestLine;
156 int lastExecution;
157 Future<void> throttleFuture;
158
159 return StreamTransformer<S, S>
160 .fromHandlers(
161 handleData: (S value, EventSink<S> sink) {
162 latestLine = value;
163
164 final int currentTime = DateTime.now().millisecondsSinceEpoch;
165 lastExecution ??= currentTime;
166 final int remainingTime = currentTime - lastExecution;
167 final int nextExecutionTime = remainingTime > waitDuration.inMilliseconds
168 ? 0
169 : waitDuration.inMilliseconds - remainingTime;
170
171 throttleFuture ??= Future<void>
172 .delayed(Duration(milliseconds: nextExecutionTime))
173 .whenComplete(() {
174 sink.add(latestLine);
175 throttleFuture = null;
176 lastExecution = DateTime.now().millisecondsSinceEpoch;
177 });
178 }
179 );
180}