defmodule ExMobileDevice.Muxd.Connection do @moduledoc false use GenStateMachine, restart: :temporary use TypedStruct alias ExMobileDevice.Muxd.Socket def default_addr(), do: {:local, "/var/run/usbmuxd"} def default_port(), do: 0 @spec start_link(Keyword.t()) :: GenStateMachine.on_start() def start_link(args) do GenStateMachine.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 GenStateMachine.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 GenStateMachine.call(conn, {:connect_thru, udid, port}) end @spec close(:gen_statem.server_ref()) :: :ok def close(conn) do GenStateMachine.stop(conn) end typedstruct do field(:controlling_process, pid()) field(:monitor_ref, reference()) field(:muxd, port()) end @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} -> {:stop, reason} end end @impl true def handle_event({:call, from}, {:connect_thru, udid, port}, _, %__MODULE__{} = data) do if did = ExMobileDevice.Muxd.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" => _}} -> {:keep_state_and_data, {:reply, from, {:error, :failed}}} error -> {:keep_state_and_data, {:reply, from, error}} end else {: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, Plist.decode(record)}}} {:ok, _} -> {:keep_state_and_data, {:reply, from, {:error, :enoent}}} _ -> {: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 end end