// Copyright 2017 The Chromium 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 'context.dart'; import 'io.dart'; const PortScanner _kLocalPortScanner = const HostPortScanner(); const int _kMaxSearchIterations = 20; PortScanner get portScanner { return context == null ? _kLocalPortScanner : context.putIfAbsent(PortScanner, () => _kLocalPortScanner); } abstract class PortScanner { const PortScanner(); /// Returns true if the specified [port] is available to bind to. Future<bool> isPortAvailable(int port); /// Returns an available ephemeral port. Future<int> findAvailablePort(); /// Returns an available port as close to [defaultPort] as possible. /// /// If [defaultPort] is available, this will return it. Otherwise, it will /// search for an avaiable port close to [defaultPort]. If it cannot find one, /// it will return any available port. Future<int> findPreferredPort(int defaultPort, { int searchStep: 2 }) async { int iterationCount = 0; while (iterationCount < _kMaxSearchIterations) { final int port = defaultPort + iterationCount * searchStep; if (await isPortAvailable(port)) return port; iterationCount++; } return findAvailablePort(); } } class HostPortScanner extends PortScanner { const HostPortScanner(); @override Future<bool> isPortAvailable(int port) async { try { // TODO(ianh): This is super racy. final ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port); await socket.close(); return true; } catch (error) { return false; } } @override Future<int> findAvailablePort() async { final ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0); final int port = socket.port; await socket.close(); return port; } }