Skip to content
Merged
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 @@ -199,6 +199,10 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy
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, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ListForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AdbPortRule!>!>!
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdDeviceProfile!>!>!
Xamarin.Android.Tools.AvdDeviceProfile
Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy
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, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ListForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AdbPortRule!>!>!
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdDeviceProfile!>!>!
Xamarin.Android.Tools.AvdDeviceProfile
Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void
Expand Down
139 changes: 139 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,145 @@ internal static IReadOnlyList<AdbPortRule> ParseReverseListOutput (IEnumerable<s
return rules;
}

/// <summary>
/// Sets up forward port forwarding via 'adb -s &lt;serial&gt; forward &lt;local&gt; &lt;remote&gt;'.
/// The host-side &lt;local&gt; socket is forwarded to the device-side &lt;remote&gt; socket,
/// the symmetric pair to <see cref="ReversePortAsync"/>.
/// </summary>
/// <param name="serial">Device serial number.</param>
/// <param name="local">Local (host-side) port spec.</param>
/// <param name="remote">Remote (device-side) port spec.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual async Task ForwardPortAsync (string serial, AdbPortSpec local, AdbPortSpec remote, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace (serial))
throw new ArgumentException ("Serial must not be empty.", nameof (serial));
if (local is null)
throw new ArgumentNullException (nameof (local));
Comment thread
rmarinho marked this conversation as resolved.
if (remote is null)
throw new ArgumentNullException (nameof (remote));
if (local.Port <= 0 || local.Port > 65535)
throw new ArgumentOutOfRangeException (nameof (local), local.Port, "Port must be between 1 and 65535.");
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, "forward", local.ToSocketSpec (), remote.ToSocketSpec ());
using var stdout = new StringWriter ();
using var stderr = new StringWriter ();
var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false);
ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} forward {local} {remote}", stderr, stdout);
}

/// <summary>
/// Removes a specific forward port forwarding rule via
/// 'adb -s &lt;serial&gt; forward --remove &lt;local&gt;'.
/// </summary>
/// <param name="serial">Device serial number.</param>
/// <param name="local">Local (host-side) port spec to remove.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public virtual async Task RemoveForwardPortAsync (string serial, AdbPortSpec local, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace (serial))
throw new ArgumentException ("Serial must not be empty.", nameof (serial));
if (local is null)
throw new ArgumentNullException (nameof (local));
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, "forward", "--remove", local.ToSocketSpec ());
using var stdout = new StringWriter ();
using var stderr = new StringWriter ();
var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false);
ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} forward --remove {local}", stderr, stdout);
}

/// <summary>
/// Removes all forward port forwarding rules for the specified device.
/// </summary>
/// <remarks>
/// The underlying <c>adb forward --remove-all</c> command (and its wire-protocol
/// equivalent <c>host-serial:&lt;serial&gt;:killforward-all</c>) operates globally on the
/// adb daemon — the <c>-s &lt;serial&gt;</c> flag does not scope it, and calling it
/// would remove forwards for every connected device. To honour the per-device
/// contract of this method, we list the forwards for <paramref name="serial"/>
/// via <see cref="ListForwardPortsAsync"/> and remove them individually via
/// <see cref="RemoveForwardPortAsync"/>.
/// </remarks>
public virtual async Task RemoveAllForwardPortsAsync (string serial, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace (serial))
throw new ArgumentException ("Serial must not be empty.", nameof (serial));

var rules = await ListForwardPortsAsync (serial, cancellationToken).ConfigureAwait (false);
foreach (var rule in rules) {
cancellationToken.ThrowIfCancellationRequested ();
await RemoveForwardPortAsync (serial, rule.Local, cancellationToken).ConfigureAwait (false);
}
Comment on lines +422 to +431

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Pattern — Excellent design choice. The list-then-remove-individually approach avoids the adb forward --remove-all global-scope pitfall, and the <remarks> documentation explaining why is exemplary — future maintainers will immediately understand the constraint. The virtual overrides also make the composition cleanly testable (as the RecordingAdbRunner tests demonstrate).

Rule: Document non-obvious design decisions

}

/// <summary>
/// Lists active forward port forwarding rules for the specified device via
/// 'adb forward --list'.
/// The underlying command always lists rules across all devices, so the
/// result is filtered to entries matching <paramref name="serial"/>.
/// </summary>
public virtual async Task<IReadOnlyList<AdbPortRule>> ListForwardPortsAsync (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, "forward", "--list");
var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false);
ProcessUtils.ThrowIfFailed (exitCode, $"adb forward --list", stderr, stdout);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Code style — The $ prefix is unnecessary here since there are no interpolation expressions. A plain string literal "adb forward --list" avoids the (slight) reader surprise.

Rule: Don't wrap a value in an interpolated string


return ParseForwardListOutput (stdout.ToString ().Split ('\n'), serial);
}

/// <summary>
/// Parses the output of 'adb forward --list'.
/// Each line is "&lt;serial&gt; &lt;local&gt; &lt;remote&gt;", e.g. "emulator-5554 tcp:5000 tcp:6000".
/// Only rules matching <paramref name="serial"/> are returned. Lines with
/// unparseable socket specs are skipped.
/// </summary>
/// <remarks>
/// Note the field-order asymmetry vs <see cref="ParseReverseListOutput"/>:
/// forward --list: &lt;serial&gt; &lt;local&gt; &lt;remote&gt;
/// reverse --list: (reverse) &lt;remote&gt; &lt;local&gt;
/// Both parsers construct an <see cref="AdbPortRule"/> whose constructor takes
/// (Remote, Local), so the order in which we pass the parsed parts differs between
/// the two parsers — keep that in mind when modifying either of them.
/// </remarks>
internal static IReadOnlyList<AdbPortRule> ParseForwardListOutput (IEnumerable<string> lines, string serial)
{
var rules = new List<AdbPortRule> ();
if (string.IsNullOrEmpty (serial))
return rules;

foreach (var line in lines) {
var trimmed = line.Trim ();
if (string.IsNullOrEmpty (trimmed))
continue;

// Expected format: "<serial> <local> <remote>" — see <remarks> above for
// the field-order asymmetry with reverse --list.
var parts = trimmed.Split ((char[]?) null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 3)
continue;

if (!string.Equals (parts [0], serial, StringComparison.Ordinal))
continue;

var local = AdbPortSpec.TryParse (parts [1]);
var remote = AdbPortSpec.TryParse (parts [2]);
if (local is { } l && remote is { } r)
rules.Add (new AdbPortRule (r, l));
Comment thread
rmarinho marked this conversation as resolved.
}

return rules;
}

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