Current section
Files
Jump to
Current section
Files
lib/ex_mobile_device/muxd/connection.ex
defmodule ExMobileDevice.Muxd.Connection do
@moduledoc false
@behaviour :gen_statem
use TypedStruct
alias ExMobileDevice.Muxd.Monitor
alias ExMobileDevice.Muxd.Socket
require Logger
@spec child_spec(any()) :: Supervisor.child_spec()
def child_spec(args) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [args]},
restart: :temporary
}
end
def default_addr, do: {:local, "/var/run/usbmuxd"}
def default_port, do: 0
@spec start_link(Keyword.t()) :: :gen_statem.start_ret()
def start_link(args) do
:gen_statem.start_link(__MODULE__, args, [])
end
@spec get_pair_record(:gen_statem.server_ref(), binary()) :: {:ok, map()} | {:error, any}
def get_pair_record(conn, udid) do
:gen_statem.call(conn, {:get_pair_record, udid})
end
@spec connect_thru(:gen_statem.server_ref(), binary(), non_neg_integer()) ::
{:ok, port()} | {:error, any()}
def connect_thru(conn, udid, port) do
:gen_statem.call(conn, {:connect_thru, udid, port})
end
@spec close(:gen_statem.server_ref()) :: :ok
def close(conn) do
:gen_statem.stop(conn)
end
typedstruct do
field(:controlling_process, pid())
field(:monitor_ref, reference())
field(:muxd, port())
end
@impl true
def callback_mode, do: :handle_event_function
@impl true
def init(args) do
addr = Keyword.get(args, :addr, default_addr())
port = Keyword.get(args, :port, default_port())
proc = Keyword.fetch!(args, :controlling_process)
case Socket.connect(addr, port) do
{:ok, socket} ->
ref = Process.monitor(proc)
{:ok, :connected, %__MODULE__{controlling_process: proc, monitor_ref: ref, muxd: socket}}
{:error, reason} ->
Logger.error("#{__MODULE__}: Error connecting to socket. #{inspect(reason)}")
{:stop, reason}
end
end
@impl true
def handle_event({:call, from}, {:connect_thru, udid, port}, _, %__MODULE__{} = data) do
if did = Monitor.get_device_id(udid) do
msg = %{"MessageType" => "Connect", "DeviceID" => did, "PortNumber" => htons(port)}
case Socket.send_and_recv(data.muxd, msg, 0) do
{:ok, %{"MessageType" => "Result", "Number" => 0}} ->
:ok = :gen_tcp.controlling_process(data.muxd, elem(from, 0))
{:stop_and_reply, :shutdown, {:reply, from, {:ok, data.muxd}}}
{:ok, %{"MessageType" => "Result", "Number" => _}} = result ->
Logger.error(
"#{__MODULE__}: Error connecting through to port #{port} for #{udid}. #{inspect(result)}"
)
{:keep_state_and_data, {:reply, from, {:error, :failed}}}
error ->
Logger.error(
"#{__MODULE__}: Error connecting through to port #{port} for #{udid}. #{inspect(error)}"
)
{:keep_state_and_data, {:reply, from, error}}
end
else
Logger.error(
"#{__MODULE__}: Could not locate device #{udid} when connecting through to port #{port}"
)
{:keep_state_and_data, {:reply, from, {:error, :enoent}}}
end
end
def handle_event({:call, from}, {:get_pair_record, udid}, _, %__MODULE__{} = data) do
msg = %{"MessageType" => "ReadPairRecord", "PairRecordID" => udid}
case Socket.send_and_recv(data.muxd, msg, 0) do
{:ok, %{"PairRecordData" => record}} ->
{:keep_state_and_data, {:reply, from, {:ok, Pealist.decode(record)}}}
{:ok, _} = result ->
Logger.error("#{__MODULE__}: Error getting pair record for #{udid}. #{inspect(result)}")
{:keep_state_and_data, {:reply, from, {:error, :enoent}}}
error ->
Logger.error("#{__MODULE__}: Error getting pair record for #{udid}. #{inspect(error)}")
{:keep_state_and_data, {:reply, from, {:error, :failed}}}
end
end
def handle_event(:info, {:DOWN, ref, :process, pid, reason}, _, %__MODULE__{
controlling_process: pid,
monitor_ref: ref
}) do
{:stop, reason}
end
defp htons(hport) do
<<nport::big-size(16)>> = <<hport::little-size(16)>>
nport
end
end