Packages
mob_dev
0.2.3
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/tunnel.ex
defmodule MobDev.Tunnel do
@moduledoc """
Manages adb port tunnels for Android devices.
For each device:
adb reverse tcp:4369 tcp:4369 — Android BEAM registers in Mac's EPMD
adb forward tcp:<dist> tcp:9100 — Mac reaches device's dist port
"""
alias MobDev.Device
# EPMD port — shared across all devices (same Mac EPMD).
@epmd_port 4369
# Base dist port — each device gets an offset so they don't collide.
@base_dist_port 9100
@doc """
Assigns a dist port and sets up adb tunnels for a device.
Returns {:ok, %Device{}} with dist_port filled in, or {:error, reason}.
"""
@spec setup(Device.t(), non_neg_integer()) :: {:ok, Device.t()} | {:error, String.t()}
def setup(device, index \\ 0)
def setup(%Device{platform: :android, serial: serial} = device, index) do
dist_port = @base_dist_port + index
with :ok <- reverse(serial, @epmd_port, @epmd_port),
:ok <- forward(serial, dist_port, @base_dist_port) do
{:ok, %{device | dist_port: dist_port, status: :tunneled}}
end
end
def setup(%Device{platform: :ios} = device, index) do
# iOS simulator shares Mac network stack — no adb tunnels needed.
# Port is offset by index so iOS and Android don't share the same dist port.
# (Android's adb forward also binds that port on Mac's loopback, causing conflict.)
# Physical iOS via iproxy is a future addition.
dist_port = @base_dist_port + index
{:ok, %{device | dist_port: dist_port, status: :tunneled}}
end
@doc "Returns the dist port for a given device index (same formula used in setup/2)."
@spec dist_port(non_neg_integer()) :: non_neg_integer()
def dist_port(index), do: @base_dist_port + index
@doc "Tears down adb tunnels for a device."
@spec teardown(Device.t()) :: :ok
def teardown(%Device{platform: :android, serial: serial, dist_port: dist_port}) do
run_adb(["-s", serial, "reverse", "--remove", "tcp:#{@epmd_port}"])
run_adb(["-s", serial, "forward", "--remove", "tcp:#{dist_port}"])
:ok
end
def teardown(%Device{platform: :ios}), do: :ok
# adb reverse tcp:remote tcp:local (device→Mac)
defp reverse(serial, device_port, local_port) do
case run_adb(["-s", serial, "reverse",
"tcp:#{device_port}", "tcp:#{local_port}"]) do
{:ok, _} -> :ok
{:error, reason} -> {:error, "reverse #{device_port}: #{reason}"}
end
end
# adb forward tcp:local tcp:remote (Mac→device)
defp forward(serial, local_port, device_port) do
case run_adb(["-s", serial, "forward",
"tcp:#{local_port}", "tcp:#{device_port}"]) do
{:ok, _} -> :ok
{:error, reason} -> {:error, "forward #{local_port}→#{device_port}: #{reason}"}
end
end
defp run_adb(args) do
case System.cmd("adb", args, stderr_to_stdout: true) do
{output, 0} -> {:ok, String.trim(output)}
{output, _} -> {:error, String.trim(output)}
end
end
end