Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 36
Add ADB reverse port forwarding support#305
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
80af3097ed82e0db1fe6bc3593448a30a7ec36090a3bf763c15af817733b98251f8c721194140File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| namespace Xamarin.Android.Tools; | ||
| /// <summary> | ||
| /// Represents an adb port forwarding rule as reported by 'adb reverse --list' or 'adb forward --list'. | ||
| /// </summary> | ||
| public record AdbPortRule (AdbPortSpec Remote, AdbPortSpec Local); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| using System; | ||
| namespace Xamarin.Android.Tools; | ||
| /// <summary> | ||
| /// Represents a port and protocol pair for adb forwarding/reverse operations. | ||
| /// </summary> | ||
| public record AdbPortSpec (AdbProtocol Protocol, int Port) | ||
| { | ||
| /// <summary> | ||
| /// Returns the adb socket spec string, e.g. "tcp:5000". | ||
| /// </summary> | ||
| public string ToSocketSpec () => Protocol switch { | ||
| AdbProtocol.Tcp => FormattableString.Invariant ($"tcp:{Port}"), | ||
| _ => throw new ArgumentOutOfRangeException (nameof (Protocol), Protocol, $"Unsupported ADB protocol: {Protocol}"), | ||
rmarinho marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. jonathanpeppers marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }; | ||
jonathanpeppers marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// <summary> | ||
| /// Parses an adb socket spec string like "tcp:5000" into an <see cref="AdbPortSpec"/>. | ||
| /// Returns null if the format is unrecognized. | ||
| /// </summary> | ||
| public static AdbPortSpec? TryParse (string? socketSpec) | ||
rmarinho marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| { | ||
| if (socketSpec is not { Length: > 0 } value || string.IsNullOrWhiteSpace (value)) | ||
| return null; | ||
| var colonIndex = value.IndexOf (':'); | ||
| if (colonIndex <= 0 || colonIndex >= value.Length - 1) | ||
| return null; | ||
| var protocolStr = value.Substring (0, colonIndex); | ||
| var portStr = value.Substring (colonIndex + 1); | ||
| if (!int.TryParse (portStr, out var port) || port <= 0 || port > 65535) | ||
| return null; | ||
| var protocol = protocolStr.ToLowerInvariant () switch { | ||
| "tcp" => (AdbProtocol?) AdbProtocol.Tcp, | ||
| _ => null, | ||
| }; | ||
| return protocol.HasValue ? new AdbPortSpec (protocol.Value, port) : null; | ||
| } | ||
| public override string ToString () => ToSocketSpec (); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| namespace Xamarin.Android.Tools; | ||
| /// <summary> | ||
| /// Protocol types supported by adb port forwarding and reverse port forwarding. | ||
| /// </summary> | ||
| public enum AdbProtocol | ||
| { | ||
| /// <summary> | ||
| /// TCP socket spec, e.g. "tcp:5000". | ||
| /// </summary> | ||
| Tcp, | ||
| } | ||
jonathanpeppers marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -244,6 +244,117 @@ public async Task StopEmulatorAsync (string serial, CancellationToken cancellati | ||
| return null; | ||
| } | ||
| /// <summary> | ||
| /// Sets up reverse port forwarding via 'adb -s <serial> reverse <remote> <local>'. | ||
| /// </summary> | ||
| /// <param name="serial">Device serial number.</param> | ||
| /// <param name="remote">Remote (device-side) port spec.</param> | ||
| /// <param name="local">Local (host-side) port spec.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| public virtual async Task ReversePortAsync (string serial, AdbPortSpec remote, AdbPortSpec local, CancellationToken cancellationToken = default) | ||
| { | ||
rmarinho marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (string.IsNullOrWhiteSpace (serial)) | ||
| throw new ArgumentException ("Serial must not be empty.", nameof (serial)); | ||
| if (remote is null) | ||
| throw new ArgumentNullException (nameof (remote)); | ||
| if (local is null) | ||
| throw new ArgumentNullException (nameof (local)); | ||
| if (remote.Port <= 0 || remote.Port > 65535) | ||
| throw new ArgumentOutOfRangeException (nameof (remote), remote.Port, "Port must be between 1 and 65535."); | ||
| if (local.Port <= 0 || local.Port > 65535) | ||
| throw new ArgumentOutOfRangeException (nameof (local), local.Port, "Port must be between 1 and 65535."); | ||
| var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", remote.ToSocketSpec (), local.ToSocketSpec ()); | ||
| using var stderr = new StringWriter (); | ||
| var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); | ||
| ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse {remote} {local}", stderr); | ||
rmarinho marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| /// <summary> | ||
| /// Removes a specific reverse port forwarding rule via | ||
| /// 'adb -s <serial> reverse --remove <remote>'. | ||
| /// </summary> | ||
| /// <param name="serial">Device serial number.</param> | ||
| /// <param name="remote">Remote (device-side) port spec to remove.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| public virtual async Task RemoveReversePortAsync (string serial, AdbPortSpec remote, CancellationToken cancellationToken = default) | ||
| { | ||
| if (string.IsNullOrWhiteSpace (serial)) | ||
| throw new ArgumentException ("Serial must not be empty.", nameof (serial)); | ||
| if (remote is null) | ||
| throw new ArgumentNullException (nameof (remote)); | ||
| if (remote.Port <= 0 || remote.Port > 65535) | ||
| throw new ArgumentOutOfRangeException (nameof (remote), remote.Port, "Port must be between 1 and 65535."); | ||
| var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--remove", remote.ToSocketSpec ()); | ||
| using var stderr = new StringWriter (); | ||
| var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); | ||
| ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --remove {remote}", stderr); | ||
rmarinho marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| /// <summary> | ||
| /// Removes all reverse port forwarding rules via | ||
| /// 'adb -s <serial> reverse --remove-all'. | ||
| /// </summary> | ||
| public virtual async Task RemoveAllReversePortsAsync (string serial, CancellationToken cancellationToken = default) | ||
| { | ||
| if (string.IsNullOrWhiteSpace (serial)) | ||
| throw new ArgumentException ("Serial must not be empty.", nameof (serial)); | ||
| var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--remove-all"); | ||
| using var stderr = new StringWriter (); | ||
| var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); | ||
| ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --remove-all", stderr); | ||
| } | ||
| /// <summary> | ||
| /// Lists all active reverse port forwarding rules via | ||
| /// 'adb -s <serial> reverse --list'. | ||
| /// </summary> | ||
| public virtual async Task<IReadOnlyList<AdbPortRule>> ListReversePortsAsync (string serial, CancellationToken cancellationToken = default) | ||
| { | ||
| if (string.IsNullOrWhiteSpace (serial)) | ||
| throw new ArgumentException ("Serial must not be empty.", nameof (serial)); | ||
| using var stdout = new StringWriter (); | ||
| using var stderr = new StringWriter (); | ||
| var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--list"); | ||
| var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); | ||
| ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --list", stderr, stdout); | ||
| return ParseReverseListOutput (stdout.ToString ().Split ('\n')); | ||
| } | ||
| /// <summary> | ||
| /// Parses the output of 'adb reverse --list'. | ||
| /// Each line is "(reverse) <remote> <local>", e.g. "(reverse) tcp:5000 tcp:5000". | ||
| /// Lines with unparseable socket specs are skipped. | ||
| /// </summary> | ||
| internal static IReadOnlyList<AdbPortRule> ParseReverseListOutput (IEnumerable<string> lines) | ||
| { | ||
| var rules = new List<AdbPortRule> (); | ||
| foreach (var line in lines) { | ||
| var trimmed = line.Trim (); | ||
| if (string.IsNullOrEmpty (trimmed)) | ||
| continue; | ||
| // Expected format: "(reverse) tcp:5000 tcp:5000" | ||
| if (!trimmed.StartsWith ("(reverse)", StringComparison.Ordinal)) | ||
| continue; | ||
| var parts = trimmed.Substring ("(reverse)".Length).Trim ().Split ((char[]?) null, StringSplitOptions.RemoveEmptyEntries); | ||
| if (parts.Length >= 2) { | ||
| var remote = AdbPortSpec.TryParse (parts [0]); | ||
| var local = AdbPortSpec.TryParse (parts [1]); | ||
| if (remote is { } r && local is { } l) | ||
| rules.Add (new AdbPortRule (r, l)); | ||
| } | ||
| } | ||
| return rules; | ||
| } | ||
| /// <summary> | ||
| /// Parses the output lines from 'adb devices -l'. | ||
| /// Accepts an <see cref="IEnumerable{T}"/> to avoid allocating a joined string. | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.