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.
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 ProtocolTransportRunCommandWithStatus:ReversePort()→reverse:forward:tcp:{remote};tcp:{local}KillReverse()→reverse:killforward:{proto}:{port}AndroidDebugLaunchProvider.CreateLocalTunnelsAsync()sets up tunnels on deploy,RemoveLocalTunnelsAsync()tears them down on session end2. VS Code Extension (
vscode-maui) — ServiceHub RPCAdbServervia ServiceHub C# host:reversePort(deviceId, remoteProtocol, remotePort, localProtocol, localPort)killReverse(deviceId, remoteProtocol, remotePort)MauiAndroidPlatform.createLocalTunnels()/cleanUpDebugSession()3. Manual CLI — No C# implementation
dotnet/androiddocs referenceadb reverse tcp:9000 tcp:9001for tracingThe Problem
Each IDE reimplements the same ADB operations. Bug fixes or behavior changes must be duplicated. The wire protocol approach in
AdbServer.csis powerful but complex — it manages raw sockets, transport negotiation, and status parsing that theadbCLI handles automatically.What This PR Does
Adds a shared, CLI-based implementation in
android-toolsthat all consumers can use:How This Simplifies Each Consumer
Visual Studio (future)
Before:
AdbServer.ReversePort()using raw wire protocol inandroid-platform-supportAfter: Can switch to
AdbRunner.ReversePortAsync()from theXamarin.Android.Tools.AndroidSdkNuGet (already consumed via submodule indotnet/android). This eliminates the need for raw socket management — theadbCLI handles transport selection, protocol negotiation, and error reporting.The
AdbServer.cswire protocol code can be gradually replaced, method by method, reducing the maintenance surface inandroid-platform-support.VS Code Extension
Before: TypeScript → ServiceHub JSON-RPC → C#
AdbServer.ReversePort()→ ADB wire protocolAfter: TypeScript →
maui android adb reverse --device <serial> --remote <port> --local <port>→AdbRunner.ReversePortAsync()→adbCLIRemoves the ServiceHub dependency for this operation entirely. The CLI command will be added downstream in
dotnet/mauiDevTools CLI.dotnet/android
Can use
AdbRunner.ReversePortAsync()directly in MSBuild tasks if tracing/profiling setup is ever automated (currently manual per docs).New API Surface
All methods are
virtualfor testability, follow existingAdbRunnerpatterns (ProcessUtils.StartProcess,ThrowIfFailed), and include parameter validation.AdbRunnermethodsReversePortAsync(serial, remotePort, localPort, ct)adb -s <serial> reverse tcp:<remote> tcp:<local>RemoveReversePortAsync(serial, remotePort, ct)adb -s <serial> reverse --remove tcp:<remote>RemoveAllReversePortsAsync(serial, ct)adb -s <serial> reverse --remove-allListReversePortsAsync(serial, ct)adb -s <serial> reverse --listNew type:
AdbReversePortRuleInternal:
ParseReverseListOutputParses
adb reverse --listoutput format:(reverse) tcp:5000 tcp:5000Key Design Decisions
CLI-based, not wire protocol — Uses
adb reverseCLI commands instead of raw ADB server wire protocol. The CLI handles transport selection, error formatting, and protocol negotiation. Simpler to maintain and test, and functionally equivalent.Port parameters as
int— Since ADB reverse only supports TCP, we takeint remotePort/localPortand build thetcp:Nspec internally. Matches the simplified overload inAdbServer.ReversePort(device, remotePort, localPort, ct).RemoveAllReversePortsAsync— Beyond what VS/VS Code do today, but useful for cleanup scenarios (session crash, stale tunnels from a previous debug session).ListReversePortsAsync— Enables diagnostic/status reporting. Neither VS nor VS Code expose this today, but it is valuable for the CLI experience (maui android adb reverse --list --json).Tests
14 new tests, all passing (252 total, 0 failures):
ParseReverseListOutput: single rule, multiple rules, empty output, no lines, non-reverse lines ignored, malformed lines, different remote/local portsMigration Path
Opportunity for dotnet/android
Today, documentation instructs users to manually set up reverse port forwarding for tracing and profiling scenarios:
With
AdbRunner.ReversePortAsync()now available in the same package thatdotnet/androidalready consumes via submodule (external/xamarin-android-tools), there is an opportunity to automate this in MSBuild tasks. For example, a hypotheticalSetupTracingTunneltask could:This would eliminate a manual step from the profiling workflow and reduce the chance of user error.
Related