Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,15 @@ Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath,
Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Xamarin.Android.Tools.AvdInfo!>!
Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdInfo!>!>!
Xamarin.Android.Tools.AdbReversePortRule
Xamarin.Android.Tools.AdbReversePortRule.AdbReversePortRule(string! Remote, string! Local) -> void
Xamarin.Android.Tools.AdbReversePortRule.Local.get -> string!
Xamarin.Android.Tools.AdbReversePortRule.Local.init -> void
Xamarin.Android.Tools.AdbReversePortRule.Remote.get -> string!
Xamarin.Android.Tools.AdbReversePortRule.Remote.init -> void
virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AdbReversePortRule!>!>!
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, int remotePort, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, string! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, int remotePort, int localPort, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, string! remote, string! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,15 @@ Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath,
Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Xamarin.Android.Tools.AvdInfo!>!
Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdInfo!>!>!
Xamarin.Android.Tools.AdbReversePortRule
Xamarin.Android.Tools.AdbReversePortRule.AdbReversePortRule(string! Remote, string! Local) -> void
Xamarin.Android.Tools.AdbReversePortRule.Local.get -> string!
Xamarin.Android.Tools.AdbReversePortRule.Local.init -> void
Xamarin.Android.Tools.AdbReversePortRule.Remote.get -> string!
Xamarin.Android.Tools.AdbReversePortRule.Remote.init -> void
virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AdbReversePortRule!>!>!
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, int remotePort, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, string! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, int remotePort, int localPort, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, string! remote, string! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
12 changes: 12 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbReversePortRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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 a single ADB reverse port forwarding rule as reported by 'adb reverse --list'.
/// Uses positional record for value equality and built-in ToString().
/// </summary>
/// <param name="Remote">The remote (device-side) socket spec, e.g. "tcp:5000".</param>
/// <param name="Local">The local (host-side) socket spec, e.g. "tcp:5000".</param>
public record AdbReversePortRule (string Remote, string Local);
135 changes: 135 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,141 @@ public async Task StopEmulatorAsync (string serial, CancellationToken cancellati
ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} emu kill", stderr);
}

/// <summary>
/// Sets up reverse port forwarding from the device to the host via
/// 'adb -s &lt;serial&gt; reverse &lt;remote&gt; &lt;local&gt;'.
/// Supports any socket spec accepted by adb (tcp:PORT, localabstract:NAME, etc.).
/// This is the core overload; the (int, int) convenience overload delegates here.
/// </summary>
/// <param name="serial">Device serial number.</param>
/// <param name="remote">Remote (device-side) socket spec, e.g. "tcp:5000" or "localabstract:foo".</param>
/// <param name="local">Local (host-side) socket spec, e.g. "tcp:5000".</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual async Task ReversePortAsync (string serial, string remote, string local, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace (serial))
throw new ArgumentException ("Serial must not be empty.", nameof (serial));
if (string.IsNullOrWhiteSpace (remote))
throw new ArgumentException ("Remote socket spec must not be empty.", nameof (remote));
if (string.IsNullOrWhiteSpace (local))
throw new ArgumentException ("Local socket spec must not be empty.", nameof (local));

var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", remote, local);
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);
}

/// <summary>
/// TCP convenience overload: sets up reverse port forwarding via
/// 'adb -s &lt;serial&gt; reverse tcp:&lt;remotePort&gt; tcp:&lt;localPort&gt;'.
/// </summary>
public virtual Task ReversePortAsync (string serial, int remotePort, int localPort, CancellationToken cancellationToken = default)
{
ValidatePort (remotePort, nameof (remotePort));
ValidatePort (localPort, nameof (localPort));
return ReversePortAsync (serial, $"tcp:{remotePort}", $"tcp:{localPort}", cancellationToken);
}

/// <summary>
/// Removes a specific reverse port forwarding rule via
/// 'adb -s &lt;serial&gt; reverse --remove &lt;remote&gt;'.
/// Supports any socket spec accepted by adb.
/// </summary>
/// <param name="serial">Device serial number.</param>
/// <param name="remote">Remote (device-side) socket spec to remove, e.g. "tcp:5000".</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual async Task RemoveReversePortAsync (string serial, string remote, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace (serial))
throw new ArgumentException ("Serial must not be empty.", nameof (serial));
if (string.IsNullOrWhiteSpace (remote))
throw new ArgumentException ("Remote socket spec must not be empty.", nameof (remote));

var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--remove", remote);
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);
}

/// <summary>
/// TCP convenience overload: removes a specific reverse port forwarding rule via
/// 'adb -s &lt;serial&gt; reverse --remove tcp:&lt;remotePort&gt;'.
/// </summary>
public virtual Task RemoveReversePortAsync (string serial, int remotePort, CancellationToken cancellationToken = default)
{
ValidatePort (remotePort, nameof (remotePort));
return RemoveReversePortAsync (serial, $"tcp:{remotePort}", cancellationToken);
}

/// <summary>
/// Removes all reverse port forwarding rules via
/// 'adb -s &lt;serial&gt; 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 &lt;serial&gt; reverse --list'.
/// </summary>
public virtual async Task<IReadOnlyList<AdbReversePortRule>> 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);

return ParseReverseListOutput (stdout.ToString ().Split ('\n'));
}

/// <summary>
/// Parses the output of 'adb reverse --list'.
/// Each line is "(reverse) &lt;remote&gt; &lt;local&gt;", e.g. "(reverse) tcp:5000 tcp:5000".
/// </summary>
internal static IReadOnlyList<AdbReversePortRule> ParseReverseListOutput (IEnumerable<string> lines)
{
var rules = new List<AdbReversePortRule> ();

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 (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2) {
rules.Add (new AdbReversePortRule (
Remote: parts [0],
Local: parts [1]
));
}
}

return rules;
}

static void ValidatePort (int port, string paramName)
{
if (port <= 0 || port > 65535)
throw new ArgumentOutOfRangeException (paramName, port, "Port must be between 1 and 65535.");
}

/// <summary>
/// Parses the output lines from 'adb devices -l'.
/// Accepts an <see cref="IEnumerable{T}"/> to avoid allocating a joined string.
Expand Down
Loading