Support mdns when attaching to proxied devices. (#146021)
Also move the vm service discovery logic into platform-specific implementation of `Device`s. This is to avoid having platform-specific code in attach.dart.
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 68f98a6..1715f65 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -18,6 +18,7 @@
import '../convert.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import 'android.dart';
@@ -801,6 +802,26 @@
}
@override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) =>
+ LogScanningVMServiceDiscoveryForAttach(
+ // If it's an Android device, attaching relies on past log searching
+ // to find the service protocol.
+ Future<DeviceLogReader>.value(getLogReader(includePastLogs: true)),
+ portForwarder: portForwarder,
+ ipv6: ipv6,
+ devicePort: filterDevicePort,
+ hostPort: expectedHostPort,
+ logger: logger,
+ );
+
+ @override
late final DevicePortForwarder? portForwarder = () {
final String? adbPath = _androidSdk.adbPath;
if (adbPath == null) {
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 9dd507b..404b116 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -21,13 +21,11 @@
import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
-import '../fuchsia/fuchsia_device.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../ios/devices.dart';
-import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
import '../project.dart';
-import '../protocol_discovery.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
@@ -286,116 +284,48 @@
: null;
Stream<Uri>? vmServiceUri;
- bool usesIpv6 = ipv6!;
+ final bool usesIpv6 = ipv6!;
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
final bool isWirelessIOSDevice = (device is IOSDevice) && device.isWirelesslyConnected;
if ((debugPort == null && debugUri == null) || isWirelessIOSDevice) {
- if (device is FuchsiaDevice) {
- final String? module = stringArg('module');
- if (module == null) {
- throwToolExit("'--module' is required for attaching to a Fuchsia device");
- }
- usesIpv6 = device.ipv6;
- FuchsiaIsolateDiscoveryProtocol? isolateDiscoveryProtocol;
- try {
- isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
- vmServiceUri = Stream<Uri>.value(await isolateDiscoveryProtocol.uri).asBroadcastStream();
- } on Exception {
- isolateDiscoveryProtocol?.dispose();
- final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
- for (final ForwardedPort port in ports) {
- await device.portForwarder.unforward(port);
+ // The device port we expect to have the debug port be listening
+ final int? devicePort = debugPort ?? debugUri?.port ?? deviceVmservicePort;
+
+ final VMServiceDiscoveryForAttach vmServiceDiscovery = device.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: stringArg('module'),
+ filterDevicePort: devicePort,
+ expectedHostPort: hostVmservicePort,
+ ipv6: usesIpv6,
+ logger: _logger,
+ );
+
+ _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
+ final Status discoveryStatus = _logger.startSpinner(
+ timeout: const Duration(seconds: 30),
+ slowWarningCallback: () {
+ // On iOS we rely on mDNS to find Dart VM Service. Remind the user to allow local network permissions on the device.
+ if (_isIOSDevice(device)) {
+ return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
+ 'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
+ 'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
+ "If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
}
- rethrow;
- }
- } else if (_isIOSDevice(device)) {
- // Protocol Discovery relies on logging. On iOS earlier than 13, logging is gathered using syslog.
- // syslog is not available for iOS 13+. For iOS 13+, Protocol Discovery gathers logs from the VMService.
- // Since we don't have access to the VMService yet, Protocol Discovery cannot be used for iOS 13+.
- // Also, wireless devices must be found using mDNS and cannot use Protocol Discovery.
- final bool compatibleWithProtocolDiscovery = (device is IOSDevice) &&
- device.majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
- !isWirelessIOSDevice;
- _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
- final Status discoveryStatus = _logger.startSpinner(
- timeout: const Duration(seconds: 30),
- slowWarningCallback: () {
- // If relying on mDNS to find Dart VM Service, remind the user to allow local network permissions.
- if (!compatibleWithProtocolDiscovery) {
- return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
- 'Click "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
- 'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
- "If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
- }
+ return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
+ },
+ );
- return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
- },
- );
+ vmServiceUri = vmServiceDiscovery.uris;
- int? devicePort;
- if (debugPort != null) {
- devicePort = debugPort;
- } else if (debugUri != null) {
- devicePort = debugUri?.port;
- } else if (deviceVmservicePort != null) {
- devicePort = deviceVmservicePort;
- }
-
- final Future<Uri?> mDNSDiscoveryFuture = MDnsVmServiceDiscovery.instance!.getVMServiceUriForAttach(
- appId,
- device,
- usesIpv6: usesIpv6,
- useDeviceIPAsHost: isWirelessIOSDevice,
- deviceVmservicePort: devicePort,
- );
-
- Future<Uri?>? protocolDiscoveryFuture;
- if (compatibleWithProtocolDiscovery) {
- final ProtocolDiscovery vmServiceDiscovery = ProtocolDiscovery.vmService(
- device.getLogReader(),
- portForwarder: device.portForwarder,
- ipv6: ipv6!,
- devicePort: devicePort,
- hostPort: hostVmservicePort,
- logger: _logger,
- );
- protocolDiscoveryFuture = vmServiceDiscovery.uri;
- }
-
- final Uri? foundUrl;
- if (protocolDiscoveryFuture == null) {
- foundUrl = await mDNSDiscoveryFuture;
- } else {
- foundUrl = await Future.any(
- <Future<Uri?>>[mDNSDiscoveryFuture, protocolDiscoveryFuture]
- );
- }
+ // Stop the timer once we receive the first uri.
+ vmServiceUri = vmServiceUri.map((Uri uri) {
discoveryStatus.stop();
-
- vmServiceUri = foundUrl == null
- ? null
- : Stream<Uri>.value(foundUrl).asBroadcastStream();
- }
- // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
- if (vmServiceUri == null) {
- final ProtocolDiscovery vmServiceDiscovery =
- ProtocolDiscovery.vmService(
- // If it's an Android device, attaching relies on past log searching
- // to find the service protocol.
- await device.getLogReader(includePastLogs: device is AndroidDevice),
- portForwarder: device.portForwarder,
- ipv6: ipv6!,
- devicePort: deviceVmservicePort,
- hostPort: hostVmservicePort,
- logger: _logger,
- );
- _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
- vmServiceUri = vmServiceDiscovery.uris;
- }
+ return uri;
+ });
} else {
vmServiceUri = Stream<Uri>
.fromFuture(
@@ -559,8 +489,7 @@
Future<void> _validateArguments() async { }
bool _isIOSDevice(Device device) {
- return (device is IOSDevice) ||
- (device is IOSSimulator) ||
+ return (device.platformType == PlatformType.ios) ||
(device is MacOSDesignedForIPadDevice);
}
}
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index d74f0ae..e020b11 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -21,6 +21,7 @@
import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../emulator.dart';
import '../features.dart';
import '../globals.dart' as globals;
@@ -1009,6 +1010,8 @@
registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService);
registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService);
registerHandler('getDiagnostics', getDiagnostics);
+ registerHandler('startVMServiceDiscoveryForAttach', startVMServiceDiscoveryForAttach);
+ registerHandler('stopVMServiceDiscoveryForAttach', stopVMServiceDiscoveryForAttach);
// Use the device manager discovery so that client provided device types
// are usable via the daemon protocol.
@@ -1325,6 +1328,41 @@
...diagnostics,
];
}
+
+ final Map<String, StreamSubscription<Uri>> _vmServiceDiscoverySubscriptions = <String, StreamSubscription<Uri>>{};
+
+ Future<String> startVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
+ final String? deviceId = _getStringArg(args, 'deviceId', required: true);
+ final String? appId = _getStringArg(args, 'appId');
+ final String? fuchsiaModule = _getStringArg(args, 'fuchsiaModule');
+ final int? filterDevicePort = _getIntArg(args, 'filterDevicePort');
+ final bool? ipv6 = _getBoolArg(args, 'ipv6');
+
+ final Device? device = await daemon.deviceDomain._getDevice(deviceId);
+ if (device == null) {
+ throw DaemonException("device '$deviceId' not found");
+ }
+
+ final String id = '${_id++}';
+
+ final VMServiceDiscoveryForAttach discovery = device.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ ipv6: ipv6 ?? false,
+ logger: globals.logger
+ );
+ _vmServiceDiscoverySubscriptions[id] = discovery.uris.listen(
+ (Uri uri) => sendEvent('device.VMServiceDiscoveryForAttach.$id', uri.toString()),
+ );
+
+ return id;
+ }
+
+ Future<void> stopVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
+ final String? id = _getStringArg(args, 'id', required: true);
+ await _vmServiceDiscoverySubscriptions.remove(id)?.cancel();
+ }
}
class DevToolsDomain extends Domain {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index c3469bb..96006cd 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -16,6 +16,7 @@
import 'build_info.dart';
import 'devfs.dart';
import 'device_port_forwarder.dart';
+import 'device_vm_service_discovery_for_attach.dart';
import 'project.dart';
import 'vmservice.dart';
import 'web/compile.dart';
@@ -737,6 +738,35 @@
/// Clear the device's logs.
void clearLogs();
+ /// Get the [VMServiceDiscoveryForAttach] instance for this device, which
+ /// discovers, and forwards any necessary ports to the vm service uri of a
+ /// running app on the device.
+ ///
+ /// If `appId` is specified, on supported platforms, the service discovery
+ /// will only return the VM service URI from the given app.
+ ///
+ /// If `fuchsiaModule` is specified, this will only return the VM service uri
+ /// from the specified Fuchsia module.
+ ///
+ /// If `filterDevicePort` is specified, this will only return the VM service
+ /// uri that matches the given port on the device.
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) =>
+ LogScanningVMServiceDiscoveryForAttach(
+ Future<DeviceLogReader>.value(getLogReader()),
+ portForwarder: portForwarder,
+ devicePort: filterDevicePort,
+ hostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ );
+
/// Start an app package on the current device.
///
/// [platformArgs] allows callers to pass platform-specific arguments to the
diff --git a/packages/flutter_tools/lib/src/device_vm_service_discovery_for_attach.dart b/packages/flutter_tools/lib/src/device_vm_service_discovery_for_attach.dart
new file mode 100644
index 0000000..5b4920b
--- /dev/null
+++ b/packages/flutter_tools/lib/src/device_vm_service_discovery_for_attach.dart
@@ -0,0 +1,110 @@
+// Copyright 2014 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 'package:async/async.dart';
+
+import 'base/logger.dart';
+import 'device.dart';
+import 'device_port_forwarder.dart';
+import 'mdns_discovery.dart';
+import 'protocol_discovery.dart';
+
+/// Discovers the VM service uri on a device, and forwards the port to the host.
+///
+/// This is mainly used during a `flutter attach`.
+abstract class VMServiceDiscoveryForAttach {
+ VMServiceDiscoveryForAttach();
+
+ /// The discovered VM service URis.
+ ///
+ /// Port forwarding is only attempted when this is invoked, for each VM
+ /// Service URI in the stream.
+ Stream<Uri> get uris;
+}
+
+/// An implementation of [VMServiceDiscoveryForAttach] that uses log scanning
+/// for the discovery.
+class LogScanningVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
+ LogScanningVMServiceDiscoveryForAttach(
+ Future<DeviceLogReader> logReader, {
+ DevicePortForwarder? portForwarder,
+ int? hostPort,
+ int? devicePort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ _protocolDiscovery = (() async => ProtocolDiscovery.vmService(
+ await logReader,
+ portForwarder: portForwarder,
+ ipv6: ipv6,
+ devicePort: devicePort,
+ hostPort: hostPort,
+ logger: logger,
+ ))();
+ }
+
+ late final Future<ProtocolDiscovery> _protocolDiscovery;
+
+ @override
+ Stream<Uri> get uris {
+ final StreamController<Uri> controller = StreamController<Uri>();
+ _protocolDiscovery.then(
+ (ProtocolDiscovery protocolDiscovery) async {
+ await controller.addStream(protocolDiscovery.uris);
+ await controller.close();
+ },
+ onError: (Object error) => controller.addError(error),
+ );
+ return controller.stream;
+ }
+}
+
+/// An implementation of [VMServiceDiscoveryForAttach] that uses mdns for the
+/// discovery.
+class MdnsVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
+ MdnsVMServiceDiscoveryForAttach({
+ required this.device,
+ this.appId,
+ required this.usesIpv6,
+ required this.useDeviceIPAsHost,
+ this.deviceVmservicePort,
+ this.hostVmservicePort,
+ });
+
+ final Device device;
+ final String? appId;
+ final bool usesIpv6;
+ final bool useDeviceIPAsHost;
+ final int? deviceVmservicePort;
+ final int? hostVmservicePort;
+
+ @override
+ Stream<Uri> get uris {
+ final Future<Uri?> mDNSDiscoveryFuture = MDnsVmServiceDiscovery.instance!.getVMServiceUriForAttach(
+ appId,
+ device,
+ usesIpv6: usesIpv6,
+ useDeviceIPAsHost: useDeviceIPAsHost,
+ deviceVmservicePort: deviceVmservicePort,
+ hostVmservicePort: hostVmservicePort,
+ );
+
+ return Stream<Uri?>.fromFuture(mDNSDiscoveryFuture).where((Uri? uri) => uri != null).cast<Uri>().asBroadcastStream();
+ }
+}
+
+/// An implementation of [VMServiceDiscoveryForAttach] that delegates to other
+/// [VMServiceDiscoveryForAttach] instances for discovery.
+class DelegateVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
+ DelegateVMServiceDiscoveryForAttach(this.delegates);
+
+ final List<VMServiceDiscoveryForAttach> delegates;
+
+ @override
+ Stream<Uri> get uris =>
+ StreamGroup.merge<Uri>(
+ delegates.map((VMServiceDiscoveryForAttach delegate) => delegate.uris));
+}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index cddf8bd..5da81c7 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -21,6 +21,7 @@
import '../build_info.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../runner/flutter_command.dart';
@@ -595,6 +596,24 @@
@override
void clearLogs() {}
+ @override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ if (fuchsiaModule == null) {
+ throwToolExit("'--module' is required for attaching to a Fuchsia device");
+ }
+ if (expectedHostPort != null) {
+ throwToolExit("'--host-vmservice-port' is not supported when attaching to a Fuchsia device");
+ }
+ return FuchsiaIsolateVMServiceDiscoveryForAttach(getIsolateDiscoveryProtocol(fuchsiaModule));
+ }
+
/// [true] if the current host address is IPv6.
late final bool ipv6 = isIPv6Address(id);
@@ -739,6 +758,30 @@
}
}
+class FuchsiaIsolateVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
+ FuchsiaIsolateVMServiceDiscoveryForAttach(this.isolateDiscoveryProtocol);
+ final FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
+
+ @override
+ Stream<Uri> get uris {
+ final Future<Uri> uriFuture = (() async {
+ // Wrapping the call in an anonymous async function for easier error handling.
+ try {
+ return await isolateDiscoveryProtocol.uri;
+ } on Exception {
+ final FuchsiaDevice device = isolateDiscoveryProtocol._device;
+ isolateDiscoveryProtocol.dispose();
+ final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
+ for (final ForwardedPort port in ports) {
+ await device.portForwarder.unforward(port);
+ }
+ rethrow;
+ }
+ })();
+ return Stream<Uri>.fromFuture(uriFuture).asBroadcastStream();
+ }
+}
+
class FuchsiaIsolateDiscoveryProtocol {
FuchsiaIsolateDiscoveryProtocol(
this._device,
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index bdf55e1..ad462a7 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -22,6 +22,7 @@
import '../convert.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../globals.dart' as globals;
import '../macos/xcdevice.dart';
import '../mdns_discovery.dart';
@@ -1027,6 +1028,43 @@
void clearLogs() { }
@override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ final bool compatibleWithProtocolDiscovery = majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
+ !isWirelesslyConnected;
+ final MdnsVMServiceDiscoveryForAttach mdnsVMServiceDiscoveryForAttach = MdnsVMServiceDiscoveryForAttach(
+ device: this,
+ appId: appId,
+ deviceVmservicePort: filterDevicePort,
+ hostVmservicePort: expectedHostPort,
+ usesIpv6: ipv6,
+ useDeviceIPAsHost: isWirelesslyConnected,
+ );
+
+ if (compatibleWithProtocolDiscovery) {
+ return DelegateVMServiceDiscoveryForAttach(<VMServiceDiscoveryForAttach>[
+ mdnsVMServiceDiscoveryForAttach,
+ super.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ expectedHostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ ),
+ ]);
+ } else {
+ return mdnsVMServiceDiscoveryForAttach;
+ }
+ }
+
+ @override
bool get supportsScreenshot {
if (isCoreDevice) {
// `idevicescreenshot` stopped working with iOS 17 / Xcode 15
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 2ee83a3..9a1d0cf 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -21,6 +21,7 @@
import '../devfs.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../globals.dart' as globals;
import '../macos/xcode.dart';
import '../project.dart';
@@ -655,6 +656,37 @@
}
@override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ final MdnsVMServiceDiscoveryForAttach mdnsVMServiceDiscoveryForAttach = MdnsVMServiceDiscoveryForAttach(
+ device: this,
+ appId: appId,
+ deviceVmservicePort: filterDevicePort,
+ hostVmservicePort: expectedHostPort,
+ usesIpv6: ipv6,
+ useDeviceIPAsHost: false,
+ );
+
+ return DelegateVMServiceDiscoveryForAttach(<VMServiceDiscoveryForAttach>[
+ mdnsVMServiceDiscoveryForAttach,
+ super.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ expectedHostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ ),
+ ]);
+ }
+
+ @override
bool get supportsScreenshot => true;
@override
diff --git a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart
index cb34310..e61cc88 100644
--- a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart
@@ -14,6 +14,7 @@
import '../build_info.dart';
import '../desktop_device.dart';
import '../device.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../ios/ios_workflow.dart';
import '../project.dart';
@@ -60,6 +61,37 @@
String? executablePathForDevice(ApplicationPackage package, BuildInfo buildInfo) => null;
@override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ final MdnsVMServiceDiscoveryForAttach mdnsVMServiceDiscoveryForAttach = MdnsVMServiceDiscoveryForAttach(
+ device: this,
+ appId: appId,
+ deviceVmservicePort: filterDevicePort,
+ hostVmservicePort: expectedHostPort,
+ usesIpv6: ipv6,
+ useDeviceIPAsHost: false,
+ );
+
+ return DelegateVMServiceDiscoveryForAttach(<VMServiceDiscoveryForAttach>[
+ mdnsVMServiceDiscoveryForAttach,
+ super.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ expectedHostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ ),
+ ]);
+ }
+
+ @override
Future<LaunchResult> startApp(
ApplicationPackage? package, {
String? mainPath,
diff --git a/packages/flutter_tools/lib/src/proxied_devices/devices.dart b/packages/flutter_tools/lib/src/proxied_devices/devices.dart
index 25f2cf4..4b4a9403 100644
--- a/packages/flutter_tools/lib/src/proxied_devices/devices.dart
+++ b/packages/flutter_tools/lib/src/proxied_devices/devices.dart
@@ -17,6 +17,7 @@
import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
+import '../device_vm_service_discovery_for_attach.dart';
import '../project.dart';
import 'debounce_data_stream.dart';
import 'file_transfer.dart';
@@ -297,6 +298,35 @@
void clearLogs() => throw UnimplementedError();
@override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) =>
+ ProxiedVMServiceDiscoveryForAttach(
+ connection,
+ id,
+ proxiedPortForwarder: proxiedPortForwarder,
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ expectedHostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ fallbackDiscovery: () => super.getVMServiceDiscoveryForAttach(
+ appId: appId,
+ fuchsiaModule: fuchsiaModule,
+ filterDevicePort: filterDevicePort,
+ expectedHostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ ),
+ );
+
+ @override
Future<LaunchResult> startApp(
PrebuiltApplicationPackage package, {
String? mainPath,
@@ -863,3 +893,84 @@
});
}
}
+
+class ProxiedVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
+ ProxiedVMServiceDiscoveryForAttach(
+ this.connection,
+ this.deviceId, {
+ required this.proxiedPortForwarder,
+ required this.fallbackDiscovery,
+ this.appId,
+ this.fuchsiaModule,
+ this.filterDevicePort,
+ this.expectedHostPort,
+ required this.ipv6,
+ required this.logger,
+ });
+
+ /// [DaemonConnection] used to communicate with the daemon.
+ final DaemonConnection connection;
+
+ final String deviceId;
+
+ final String? appId;
+ final String? fuchsiaModule;
+ final int? filterDevicePort;
+ final int? expectedHostPort;
+ final bool ipv6;
+ final Logger logger;
+
+ final ProxiedPortForwarder proxiedPortForwarder;
+
+ VMServiceDiscoveryForAttach Function() fallbackDiscovery;
+
+ Stream<Uri>? _uris;
+
+ @override
+ Stream<Uri> get uris {
+ if (_uris == null) {
+ String? requestId;
+ final StreamController<Uri> controller = StreamController<Uri>();
+
+ controller.onListen = () {
+ connection.sendRequest('device.startVMServiceDiscoveryForAttach', <String, Object?>{
+ 'deviceId': deviceId,
+ 'appId': appId,
+ 'fuchsiaModule': fuchsiaModule,
+ 'filterDevicePort': filterDevicePort,
+ 'ipv6': ipv6,
+ }).then(
+ (Object? response) async {
+ requestId = _cast<String>(response);
+ final Stream<Uri> vmService = connection
+ .listenToEvent('device.VMServiceDiscoveryForAttach.$requestId')
+ .asyncMap((DaemonEventData event) async {
+ // Forward the port.
+ final Uri remoteUri = Uri.parse(_cast<String>(event.data));
+ final int port = remoteUri.port;
+ final int localPort = await proxiedPortForwarder.forward(port, hostPort: expectedHostPort, ipv6: ipv6);
+ return remoteUri.replace(port: localPort);
+ });
+ await controller.addStream(vmService);
+ },
+ onError: (Object e) {
+ // Daemon throws string types.
+ if (e is String && e.contains('command not understood')) {
+ // Use a fallback if the daemon does not support VM service discovery.
+ controller.addStream(fallbackDiscovery().uris);
+ } else {
+ controller.addError(e);
+ }
+ },
+ );
+ };
+ controller.onCancel = () {
+ if (requestId != null) {
+ connection.sendRequest('device.stopVMServiceDiscoveryForAttach', <String, Object?>{'id': requestId});
+ }
+ };
+ _uris = controller.stream;
+ }
+ return _uris!;
+ }
+}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index ca9c45d..dfd23e7 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -22,6 +22,7 @@
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
+import 'package:flutter_tools/src/device_vm_service_discovery_for_attach.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
@@ -204,8 +205,8 @@
processInfo: processInfo,
fileSystem: testFileSystem,
)).run(<String>['attach']);
+ await completer.future;
await Future.wait<void>(<Future<void>>[
- completer.future,
fakeLogReader.dispose(),
loggerSubscription.cancel(),
]);
@@ -275,8 +276,8 @@
processInfo: processInfo,
fileSystem: testFileSystem,
)).run(<String>['attach', '--local-engine-src-path=$localEngineSrc', '--local-engine=$localEngineDir', '--local-engine-host=$localEngineDir']);
+ await completer.future;
await Future.wait<void>(<Future<void>>[
- completer.future,
fakeLogReader.dispose(),
loggerSubscription.cancel(),
]);
@@ -331,12 +332,15 @@
)).run(<String>['attach']);
await fakeLogReader.dispose();
- expect(portForwarder.devicePort, devicePort);
- expect(portForwarder.hostPort, hostPort);
- expect(hotRunnerFactory.devices, hasLength(1));
+ // Listen to the URI before checking port forwarder. Port forwarding
+ // is done as a side effect when generating the uri.
final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
final Uri? vmServiceUri = await flutterDevice.vmServiceUris?.first;
expect(vmServiceUri.toString(), 'http://127.0.0.1:$hostPort/xyz/');
+
+ expect(portForwarder.devicePort, devicePort);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
@@ -396,13 +400,15 @@
)).run(<String>['attach']);
await fakeLogReader.dispose();
- expect(portForwarder.devicePort, null);
- expect(portForwarder.hostPort, hostPort);
- expect(hotRunnerFactory.devices, hasLength(1));
-
+ // Listen to the URI before checking port forwarder. Port forwarding
+ // is done as a side effect when generating the uri.
final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
final Uri? vmServiceUri = await flutterDevice.vmServiceUris?.first;
expect(vmServiceUri.toString(), 'http://111.111.111.111:123/xyz/');
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
@@ -467,13 +473,15 @@
)).run(<String>['attach', '--debug-port', '123']);
await fakeLogReader.dispose();
- expect(portForwarder.devicePort, null);
- expect(portForwarder.hostPort, hostPort);
- expect(hotRunnerFactory.devices, hasLength(1));
-
+ // Listen to the URI before checking port forwarder. Port forwarding
+ // is done as a side effect when generating the uri.
final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
final Uri? vmServiceUri = await flutterDevice.vmServiceUris?.first;
expect(vmServiceUri.toString(), 'http://111.111.111.111:123/xyz/');
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
@@ -542,13 +550,15 @@
)).run(<String>['attach', '--debug-url', 'https://0.0.0.0:123']);
await fakeLogReader.dispose();
- expect(portForwarder.devicePort, null);
- expect(portForwarder.hostPort, hostPort);
- expect(hotRunnerFactory.devices, hasLength(1));
-
+ // Listen to the URI before checking port forwarder. Port forwarding
+ // is done as a side effect when generating the uri.
final FlutterDevice flutterDevice = hotRunnerFactory.devices.first;
final Uri? vmServiceUri = await flutterDevice.vmServiceUris?.first;
expect(vmServiceUri.toString(), 'http://111.111.111.111:123/xyz/');
+
+ expect(portForwarder.devicePort, null);
+ expect(portForwarder.hostPort, hostPort);
+ expect(hotRunnerFactory.devices, hasLength(1));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
@@ -1464,6 +1474,24 @@
@override
bool get ephemeral => true;
+
+ @override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) =>
+ LogScanningVMServiceDiscoveryForAttach(
+ Future<DeviceLogReader>.value(getLogReader()),
+ portForwarder: portForwarder,
+ devicePort: filterDevicePort,
+ hostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ );
}
class FakeIOSDevice extends Fake implements IOSDevice {
@@ -1527,6 +1555,43 @@
@override
bool get ephemeral => true;
+
+ @override
+ VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
+ String? appId,
+ String? fuchsiaModule,
+ int? filterDevicePort,
+ int? expectedHostPort,
+ required bool ipv6,
+ required Logger logger,
+ }) {
+ final bool compatibleWithProtocolDiscovery = majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
+ !isWirelesslyConnected;
+ final MdnsVMServiceDiscoveryForAttach mdnsVMServiceDiscoveryForAttach = MdnsVMServiceDiscoveryForAttach(
+ device: this,
+ appId: appId,
+ deviceVmservicePort: filterDevicePort,
+ hostVmservicePort: expectedHostPort,
+ usesIpv6: ipv6,
+ useDeviceIPAsHost: isWirelesslyConnected,
+ );
+
+ if (compatibleWithProtocolDiscovery) {
+ return DelegateVMServiceDiscoveryForAttach(<VMServiceDiscoveryForAttach>[
+ mdnsVMServiceDiscoveryForAttach,
+ LogScanningVMServiceDiscoveryForAttach(
+ Future<DeviceLogReader>.value(getLogReader()),
+ portForwarder: portForwarder,
+ devicePort: filterDevicePort,
+ hostPort: expectedHostPort,
+ ipv6: ipv6,
+ logger: logger,
+ ),
+ ]);
+ } else {
+ return mdnsVMServiceDiscoveryForAttach;
+ }
+ }
}
class FakeMDnsClient extends Fake implements MDnsClient {
diff --git a/packages/flutter_tools/test/general.shard/device_vm_service_discovery_for_attach_test.dart b/packages/flutter_tools/test/general.shard/device_vm_service_discovery_for_attach_test.dart
new file mode 100644
index 0000000..598a660
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/device_vm_service_discovery_for_attach_test.dart
@@ -0,0 +1,132 @@
+// Copyright 2014 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 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/device_port_forwarder.dart';
+import 'package:flutter_tools/src/device_vm_service_discovery_for_attach.dart';
+import 'package:test/fake.dart';
+
+import '../src/common.dart';
+import '../src/fake_devices.dart';
+
+void main() {
+ group('LogScanningVMServiceDiscoveryForAttach', () {
+ testWithoutContext('can discover the port', () async {
+ final FakeDeviceLogReader logReader = FakeDeviceLogReader();
+ final LogScanningVMServiceDiscoveryForAttach discovery = LogScanningVMServiceDiscoveryForAttach(
+ Future<FakeDeviceLogReader>.value(logReader),
+ ipv6: false,
+ logger: BufferLogger.test(),
+ );
+
+ logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999');
+
+ expect(await discovery.uris.first, Uri.parse('http://127.0.0.1:9999'));
+ });
+
+ testWithoutContext('ignores the port that does not match devicePort', () async {
+ final FakeDeviceLogReader logReader = FakeDeviceLogReader();
+ final LogScanningVMServiceDiscoveryForAttach discovery = LogScanningVMServiceDiscoveryForAttach(
+ Future<FakeDeviceLogReader>.value(logReader),
+ devicePort: 9998,
+ ipv6: false,
+ logger: BufferLogger.test(),
+ );
+
+ logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999');
+ logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9998');
+
+ expect(await discovery.uris.first, Uri.parse('http://127.0.0.1:9998'));
+ });
+
+ testWithoutContext('forwards the port if given a port forwarder', () async {
+ final FakeDeviceLogReader logReader = FakeDeviceLogReader();
+ final FakePortForwarder portForwarder = FakePortForwarder(9900);
+ final LogScanningVMServiceDiscoveryForAttach discovery = LogScanningVMServiceDiscoveryForAttach(
+ Future<FakeDeviceLogReader>.value(logReader),
+ portForwarder: portForwarder,
+ ipv6: false,
+ logger: BufferLogger.test(),
+ );
+
+ logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999');
+
+ expect(await discovery.uris.first, Uri.parse('http://127.0.0.1:9900'));
+ expect(portForwarder.forwardDevicePort, 9999);
+ expect(portForwarder.forwardHostPort, null);
+ });
+
+ testWithoutContext('uses the host port if given', () async {
+ final FakeDeviceLogReader logReader = FakeDeviceLogReader();
+ final FakePortForwarder portForwarder = FakePortForwarder(9900);
+ final LogScanningVMServiceDiscoveryForAttach discovery = LogScanningVMServiceDiscoveryForAttach(
+ Future<FakeDeviceLogReader>.value(logReader),
+ portForwarder: portForwarder,
+ hostPort: 9901,
+ ipv6: false,
+ logger: BufferLogger.test(),
+ );
+
+ logReader.addLine('The Dart VM service is listening on http://127.0.0.1:9999');
+
+ expect(await discovery.uris.first, Uri.parse('http://127.0.0.1:9900'));
+ expect(portForwarder.forwardDevicePort, 9999);
+ expect(portForwarder.forwardHostPort, 9901);
+ });
+ });
+
+ group('DelegateVMServiceDiscoveryForAttach', () {
+ late List<Uri> uris1;
+ late List<Uri> uris2;
+ late FakeVmServiceDiscoveryForAttach fakeDiscovery1;
+ late FakeVmServiceDiscoveryForAttach fakeDiscovery2;
+ late DelegateVMServiceDiscoveryForAttach delegateDiscovery;
+
+ setUp(() {
+ uris1 = <Uri>[];
+ uris2 = <Uri>[];
+ fakeDiscovery1 = FakeVmServiceDiscoveryForAttach(uris1);
+ fakeDiscovery2 = FakeVmServiceDiscoveryForAttach(uris2);
+ delegateDiscovery = DelegateVMServiceDiscoveryForAttach(<VMServiceDiscoveryForAttach>[fakeDiscovery1, fakeDiscovery2]);
+ });
+
+ testWithoutContext('uris returns from both delegates', () async {
+ uris1.add(Uri.parse('http://127.0.0.1:1'));
+ uris1.add(Uri.parse('http://127.0.0.2:2'));
+ uris2.add(Uri.parse('http://127.0.0.3:3'));
+ uris2.add(Uri.parse('http://127.0.0.4:4'));
+
+ expect(await delegateDiscovery.uris.toList(), unorderedEquals(<Uri>[
+ Uri.parse('http://127.0.0.1:1'),
+ Uri.parse('http://127.0.0.2:2'),
+ Uri.parse('http://127.0.0.3:3'),
+ Uri.parse('http://127.0.0.4:4'),
+ ]));
+ });
+ });
+}
+
+class FakePortForwarder extends Fake implements DevicePortForwarder {
+ FakePortForwarder(this.forwardReturnValue);
+
+ int? forwardDevicePort;
+ int? forwardHostPort;
+ final int forwardReturnValue;
+
+ @override
+ Future<int> forward(int devicePort, { int? hostPort }) async {
+ forwardDevicePort = devicePort;
+ forwardHostPort = hostPort;
+ return forwardReturnValue;
+ }
+}
+
+class FakeVmServiceDiscoveryForAttach extends Fake implements VMServiceDiscoveryForAttach {
+ FakeVmServiceDiscoveryForAttach(this._uris);
+
+ final List<Uri> _uris;
+
+ @override
+ Stream<Uri> get uris => Stream<Uri>.fromIterable(_uris);
+}
diff --git a/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart b/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
index 4960fe9..4fae899 100644
--- a/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
@@ -14,6 +14,7 @@
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/daemon.dart';
import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/device_vm_service_discovery_for_attach.dart';
import 'package:flutter_tools/src/proxied_devices/devices.dart';
import 'package:flutter_tools/src/proxied_devices/file_transfer.dart';
import 'package:test/fake.dart';
@@ -803,6 +804,187 @@
expect(localDds.shutdownCalled, true);
});
});
+
+ group('ProxiedVMServiceDiscoveryForAttach', () {
+ testWithoutContext('sends the request and forwards the port', () async {
+ final FakeProxiedPortForwarder portForwarder = FakeProxiedPortForwarder();
+ portForwarder.forwardReturnValue = 400;
+ final ProxiedVMServiceDiscoveryForAttach discovery = ProxiedVMServiceDiscoveryForAttach(
+ clientDaemonConnection,
+ 'test_device',
+ proxiedPortForwarder: portForwarder,
+ fallbackDiscovery: () => throw UnimplementedError(),
+ ipv6: false,
+ logger: bufferLogger,
+ );
+
+ final Completer<Uri> uriCompleter = Completer<Uri>();
+
+ // Start listening on the stream to trigger sending the request.
+ discovery.uris.listen(uriCompleter.complete);
+
+ final Stream<DaemonMessage> broadcastOutput = serverDaemonConnection.incomingCommands.asBroadcastStream();
+ final DaemonMessage startMessage = await broadcastOutput.first;
+ expect(startMessage.data['id'], isNotNull);
+ expect(startMessage.data['method'], 'device.startVMServiceDiscoveryForAttach');
+ expect(startMessage.data['params'], <String, Object?>{
+ 'deviceId': 'test_device',
+ 'appId': null,
+ 'fuchsiaModule': null,
+ 'filterDevicePort': null,
+ 'ipv6': false,
+ });
+
+ serverDaemonConnection.sendResponse(startMessage.data['id']!, 'request_id');
+ serverDaemonConnection.sendEvent('device.VMServiceDiscoveryForAttach.request_id', 'http://127.0.0.1:300/auth_code');
+
+ expect(await uriCompleter.future, Uri.parse('http://127.0.0.1:400/auth_code'));
+ expect(portForwarder.forwardedDevicePort, 300);
+ expect(portForwarder.forwardedHostPort, null);
+ });
+
+ testWithoutContext('sends additional information, and forwards the correct port', () async {
+ final FakeProxiedPortForwarder portForwarder = FakeProxiedPortForwarder();
+ portForwarder.forwardReturnValue = 400;
+ final ProxiedVMServiceDiscoveryForAttach discovery = ProxiedVMServiceDiscoveryForAttach(
+ clientDaemonConnection,
+ 'test_device',
+ proxiedPortForwarder: portForwarder,
+ fallbackDiscovery: () => throw UnimplementedError(),
+ appId: 'test_app_id',
+ fuchsiaModule: 'test_fuchsia_module',
+ filterDevicePort: 100,
+ expectedHostPort: 200,
+ ipv6: false,
+ logger: bufferLogger,
+ );
+
+ final Completer<Uri> uriCompleter = Completer<Uri>();
+
+ // Start listening on the stream to trigger sending the request.
+ discovery.uris.listen(uriCompleter.complete);
+
+ final Stream<DaemonMessage> broadcastOutput = serverDaemonConnection.incomingCommands.asBroadcastStream();
+ final DaemonMessage startMessage = await broadcastOutput.first;
+ expect(startMessage.data['id'], isNotNull);
+ expect(startMessage.data['method'], 'device.startVMServiceDiscoveryForAttach');
+ expect(startMessage.data['params'], <String, Object?>{
+ 'deviceId': 'test_device',
+ 'appId': 'test_app_id',
+ 'fuchsiaModule': 'test_fuchsia_module',
+ 'filterDevicePort': 100,
+ 'ipv6': false,
+ });
+
+ serverDaemonConnection.sendResponse(startMessage.data['id']!, 'request_id');
+ serverDaemonConnection.sendEvent('device.VMServiceDiscoveryForAttach.request_id', 'http://127.0.0.1:300/auth_code');
+
+ expect(await uriCompleter.future, Uri.parse('http://127.0.0.1:400/auth_code'));
+ expect(portForwarder.forwardedDevicePort, 300);
+ expect(portForwarder.forwardedHostPort, 200);
+ });
+
+ testWithoutContext('use the fallback discovery if the remote daemon does not support proxied discovery', () async {
+ final FakeProxiedPortForwarder portForwarder = FakeProxiedPortForwarder();
+ final Stream<Uri> fallbackUri = Stream<Uri>.value(Uri.parse('http://127.0.0.1:500/fallback_auth_code'));
+ final ProxiedVMServiceDiscoveryForAttach discovery = ProxiedVMServiceDiscoveryForAttach(
+ clientDaemonConnection,
+ 'test_device',
+ proxiedPortForwarder: portForwarder,
+ fallbackDiscovery: () => FakeVMServiceDiscoveryForAttach(fallbackUri),
+ ipv6: false,
+ logger: bufferLogger,
+ );
+
+ final Completer<Uri> uriCompleter = Completer<Uri>();
+
+ // Start listening on the stream to trigger sending the request.
+ discovery.uris.listen(uriCompleter.complete);
+
+ final Stream<DaemonMessage> broadcastOutput = serverDaemonConnection.incomingCommands.asBroadcastStream();
+ final DaemonMessage startMessage = await broadcastOutput.first;
+ expect(startMessage.data['id'], isNotNull);
+ expect(startMessage.data['method'], 'device.startVMServiceDiscoveryForAttach');
+ expect(startMessage.data['params'], <String, Object?>{
+ 'deviceId': 'test_device',
+ 'appId': null,
+ 'fuchsiaModule': null,
+ 'filterDevicePort': null,
+ 'ipv6': false,
+ });
+ serverDaemonConnection.sendErrorResponse(startMessage.data['id']!, 'command not understood: device.startDartDevelopmentService', StackTrace.current);
+
+ expect(await uriCompleter.future, Uri.parse('http://127.0.0.1:500/fallback_auth_code'));
+ expect(portForwarder.forwardedDevicePort, null);
+ expect(portForwarder.forwardedHostPort, null);
+ });
+
+ testWithoutContext('forwards other error from the daemon', () async {
+ final FakeProxiedPortForwarder portForwarder = FakeProxiedPortForwarder();
+ final Stream<Uri> fallbackUri = Stream<Uri>.value(Uri.parse('http://127.0.0.1:500/fallback_auth_code'));
+ final ProxiedVMServiceDiscoveryForAttach discovery = ProxiedVMServiceDiscoveryForAttach(
+ clientDaemonConnection,
+ 'test_device',
+ proxiedPortForwarder: portForwarder,
+ fallbackDiscovery: () => FakeVMServiceDiscoveryForAttach(fallbackUri),
+ ipv6: false,
+ logger: bufferLogger,
+ );
+
+ // Start listening on the stream to trigger sending the request.
+ final Future<Uri> uriFuture = discovery.uris.first;
+
+ final Stream<DaemonMessage> broadcastOutput = serverDaemonConnection.incomingCommands.asBroadcastStream();
+ final DaemonMessage startMessage = await broadcastOutput.first;
+ expect(startMessage.data['id'], isNotNull);
+ expect(startMessage.data['method'], 'device.startVMServiceDiscoveryForAttach');
+ expect(startMessage.data['params'], <String, Object?>{
+ 'deviceId': 'test_device',
+ 'appId': null,
+ 'fuchsiaModule': null,
+ 'filterDevicePort': null,
+ 'ipv6': false,
+ });
+ serverDaemonConnection.sendErrorResponse(startMessage.data['id']!, 'other error', StackTrace.current);
+
+ expect(uriFuture, throwsA('other error'));
+ expect(portForwarder.forwardedDevicePort, null);
+ expect(portForwarder.forwardedHostPort, null);
+ });
+
+ testWithoutContext('forwards the port forwarder error', () async {
+ final FakeProxiedPortForwarder portForwarder = FakeProxiedPortForwarder();
+ portForwarder.forwardThrowException = TestException();
+ final ProxiedVMServiceDiscoveryForAttach discovery = ProxiedVMServiceDiscoveryForAttach(
+ clientDaemonConnection,
+ 'test_device',
+ proxiedPortForwarder: portForwarder,
+ fallbackDiscovery: () => throw UnimplementedError(),
+ ipv6: false,
+ logger: bufferLogger,
+ );
+
+ // Start listening on the stream to trigger sending the request.
+ final Future<Uri> uriFuture = discovery.uris.first;
+
+ final Stream<DaemonMessage> broadcastOutput = serverDaemonConnection.incomingCommands.asBroadcastStream();
+ final DaemonMessage startMessage = await broadcastOutput.first;
+ expect(startMessage.data['id'], isNotNull);
+ expect(startMessage.data['method'], 'device.startVMServiceDiscoveryForAttach');
+ expect(startMessage.data['params'], <String, Object?>{
+ 'deviceId': 'test_device',
+ 'appId': null,
+ 'fuchsiaModule': null,
+ 'filterDevicePort': null,
+ 'ipv6': false,
+ });
+
+ serverDaemonConnection.sendResponse(startMessage.data['id']!, 'request_id');
+ serverDaemonConnection.sendEvent('device.VMServiceDiscoveryForAttach.request_id', 'http://127.0.0.1:300/auth_code');
+
+ expect(uriFuture, throwsA(isA<TestException>()));
+ });
+ });
}
class FakeDaemonStreams implements DaemonStreams {
@@ -934,6 +1116,7 @@
int? originalRemotePortReturnValue;
int? receivedLocalForwardedPort;
+ Exception? forwardThrowException;
int? forwardReturnValue;
int? forwardedDevicePort;
int? forwardedHostPort;
@@ -950,6 +1133,9 @@
forwardedDevicePort = devicePort;
forwardedHostPort = hostPort;
forwardedIpv6 = ipv6;
+ if (forwardThrowException != null) {
+ throw forwardThrowException!;
+ }
return forwardReturnValue!;
}
}
@@ -999,3 +1185,12 @@
@override
Future<Uint8List> binaryForRebuilding(File file, List<FileDeltaBlock> delta) async => binary!;
}
+
+class FakeVMServiceDiscoveryForAttach extends Fake implements VMServiceDiscoveryForAttach {
+ FakeVMServiceDiscoveryForAttach(this.uris);
+
+ @override
+ Stream<Uri> uris;
+}
+
+class TestException implements Exception {}