Conversation
Add ReversePortAsync, RemoveReversePortAsync, RemoveAllReversePortsAsync, and ListReversePortsAsync methods to AdbRunner for managing reverse port forwarding rules. These APIs enable the MAUI DevTools CLI to manage hot-reload tunnels without going through ServiceHub. New type AdbReversePortRule represents entries from 'adb reverse --list'. Internal ParseReverseListOutput handles parsing the output format. Includes 14 new tests covering parsing and parameter validation. Closes #303 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds reverse port forwarding support to the Android SDK runner layer so downstream tooling (MAUI DevTools CLI / VS Code MAUI extension) can manage Hot Reload tunnels directly via library APIs.
Changes:
- Added
AdbRunnerAPIs foradb reverseoperations (add/remove/remove-all/list) plus parsing forreverse --listoutput. - Introduced new public model type
AdbReversePortRuleto represent a listed reverse rule. - Updated PublicAPI unshipped files and added unit tests for parsing and parameter validation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs | Adds tests for reverse list parsing and basic parameter validation for new APIs |
| src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs | Implements new reverse port forwarding methods and output parsing helper |
| src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbReversePortRule.cs | Adds public DTO for reverse port rules |
| src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt | Declares new public API surface for netstandard2.0 |
| src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt | Declares new public API surface for net10.0 |
You can also share your feedback on Copilot code review. Take the survey.
| [Test] | ||
| public void ReversePortAsync_PortAbove65535_ThrowsArgumentOutOfRange () | ||
| { | ||
| var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); | ||
| Assert.ThrowsAsync<System.ArgumentOutOfRangeException> ( | ||
| async () => await runner.ReversePortAsync ("emulator-5554", 70000, 5000)); | ||
| } |
| 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); |
| Xamarin.Android.Tools.AdbReversePortRule.AdbReversePortRule() -> 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 |
There was a problem hiding this comment.
I question the API design:
public class AdbReversePortRule
{
public string Remote { get; init; } // e.g. "tcp:5000"
public string Local { get; init; } // e.g. "tcp:5000"
}C# is strongly typed, we shouldn't be using strings like tcp:5000.
We should have an enum as a separate property to specify if it is tcp (and fill in the other options in the enum).
Then we should have an int property for the port.
Can't you also do adb forward?
So should this be:
public record AdbPortAndProtocol (/* this is the enum*/ AdbProtocol Protocol, int Port);
public record AdbReverseRule (AdbPortAndProtocol Remote, AdbPortAndProtocol Local);
public record AdbForwardingRule (AdbPortAndProtocol Remote, AdbPortAndProtocol Local);But then I don't know if I like having two records for reverse & forward. Maybe it should be a single record + another enum that says if it is Forward/Reverse.
Based on multi-model code review (GPT-5.1 + Gemini-3-Pro): - Convert AdbReversePortRule from class to positional record for value equality and concise construction - Add string socket-spec overloads for ReversePortAsync and RemoveReversePortAsync to support non-TCP protocols (e.g., localabstract:, localfilesystem:) matching existing patterns in ClientTools.Platform and vscode-maui - Extract ValidatePort helper for consistent validation - Int overloads now delegate to string overloads as convenience wrappers - Add 8 new tests: NonTcpSpecs, WindowsLineEndings, ValueEquality, Deconstruct, ToString, and string overload validation tests (total: 25 reverse-port tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Add reverse port forwarding APIs to
AdbRunner, enabling the MAUI DevTools CLI to manage hot-reload tunnels without going through ServiceHub.Closes #303
Context
Reverse port forwarding (
adb reverse) is used across three separate codebases today, each with its own implementation:1. Visual Studio (
ClientTools.Platform) — ADB Wire ProtocolAdbServer.cs: raw wire protocol viaTransportRunCommandWithStatus2. VS Code Extension (
vscode-maui) — ServiceHub RPCAdbServervia ServiceHub JSON-RPC3. Manual CLI — No C# implementation
dotnet/androiddocs referenceadb reverse tcp:9000 tcp:9001for tracingThe Problem
Each IDE reimplements the same ADB operations. The wire protocol approach is powerful but complex.
What This PR Does
Adds a shared, CLI-based implementation in
android-tools:New API Surface
AdbReversePortRule— Positional RecordValue type with structural equality, deconstruction (net10.0), and built-in
ToString().AdbRunnermethodsReversePortAsync(serial, string remote, string local, ct)adb -s <serial> reverse <remote> <local>ReversePortAsync(serial, int remotePort, int localPort, ct)adb -s <serial> reverse tcp:<r> tcp:<l>RemoveReversePortAsync(serial, string remote, ct)adb -s <serial> reverse --remove <remote>RemoveReversePortAsync(serial, int remotePort, ct)adb -s <serial> reverse --remove tcp:<r>RemoveAllReversePortsAsync(serial, ct)adb -s <serial> reverse --remove-allListReversePortsAsync(serial, ct)adb -s <serial> reverse --listDesign: String overloads are core implementations accepting full ADB socket specs (
tcp:5000,localabstract:name,localfilesystem:/path). Int overloads delegate to string overloads withtcp:{port}formatting. Matches existing patterns inClientTools.Platformandvscode-maui.Key Design Decisions
AvdInfo/SdkPackageRemoveAllReversePortsAsync— Cleanup after session crash/stale tunnelsListReversePortsAsync— Diagnostic reporting for CLITests
25 tests, all passing:
ParseReverseListOutput: single/multiple rules, empty output, non-reverse lines, malformed lines, non-TCP specs, Windows line endingsAdbReversePortRulerecord: value equality, deconstruction, ToStringMigration Path
Related