Current section

Files

Jump to
exmobiledevice lib ex_mobile_device services lockdown.ex
Raw

lib/ex_mobile_device/services/lockdown.ex

defmodule ExMobileDevice.Services.Lockdown do
@moduledoc false
use GenStateMachine, restart: :temporary
use TypedStruct
alias ExMobileDevice.Muxd
alias ExMobileDevice.Services
require Logger
@lockdown_port 62_078
def start_link(args) do
GenStateMachine.start_link(__MODULE__, args)
end
def get_info(conn, opts \\ []) do
args =
Keyword.take(opts, [:domain, :key])
|> Map.new(fn {k, v} -> {String.capitalize(to_string(k)), v} end)
GenStateMachine.call(conn, {:get_value, args})
end
def start_session(conn) do
GenStateMachine.call(conn, :start_session)
end
def stop_session(conn) do
GenStateMachine.call(conn, :stop_session)
end
def start_service(conn, service, opts) do
GenStateMachine.call(conn, {:start_service, service, opts})
end
def close(conn) do
GenStateMachine.stop(conn)
end
typedstruct do
field(:proc, pid())
field(:sock, port())
field(:sslsock, any())
field(:mref, reference())
field(:prec, map())
field(:sid, binary() | nil)
end
@impl true
def init(args) do
proc = Keyword.fetch!(args, :controlling_process)
udid = Keyword.fetch!(args, :udid)
with {:ok, muxd} <- Muxd.connect(),
{:ok, prec} <- maybe_get_pair_record(muxd, udid),
{:ok, sock} <- Muxd.connect_thru(muxd, udid, @lockdown_port) do
:ok = :inet.setopts(sock, packet: 4)
ref = Process.monitor(proc)
{:ok, :connected, %__MODULE__{sock: sock, mref: ref, prec: prec}}
end
end
@impl true
def handle_event({:call, from}, {:get_value, props}, _, %__MODULE__{} = data) do
reply =
with {:ok, response} <- Services.rpc(sock(data), "GetValue", props) do
{:ok, response["Value"]}
end
{:keep_state_and_data, {:reply, from, reply}}
end
def handle_event({:call, from}, :start_session, _, %__MODULE__{prec: nil}) do
{:keep_state_and_data, {:reply, from, {:error, :no_pairing_record}}}
end
def handle_event({:call, from}, :start_session, _, %__MODULE__{sid: sid})
when not is_nil(sid) do
{:keep_state_and_data, {:reply, from, {:error, :already_started}}}
end
def handle_event({:call, from}, :start_session, _, %__MODULE__{sid: nil, sslsock: nil} = data) do
case Services.rpc(sock(data), "StartSession", Map.take(data.prec, ["SystemBUID", "HostID"])) do
{:ok, %{"Request" => "StartSession", "EnableSessionSSL" => ssl?, "SessionID" => sid}} ->
if ssl? do
{:ok, sslsock} = ExMobileDevice.Ssl.connect(data.sock, data.prec)
{:keep_state, %__MODULE__{data | sslsock: sslsock, sid: sid}, {:reply, from, :ok}}
else
{:keep_state, %__MODULE__{data | sid: sid}, {:reply, from, :ok}}
end
{:ok, %{"Request" => "StartSession", "Error" => error}} ->
{:keep_state_and_data, {:reply, from, {:error, error}}}
error ->
Logger.error("#{__MODULE__}: Unexpected response from start session: #{inspect(error)}")
{:keep_state_and_data, {:reply, from, {:error, :failed}}}
end
end
def handle_event({:call, from}, :stop_session, _, %__MODULE__{sid: nil}) do
{:keep_state_and_data, {:reply, from, {:error, :no_session}}}
end
def handle_event({:call, from}, :stop_session, _, %__MODULE__{sid: sid} = data) do
{:ok, %{"Request" => "StopSession"}} =
Services.rpc(sock(data), "StopSession", %{"SessionID" => sid})
{:ok, sock} = :ssl.close(data.sslsock, {self(), :infinity})
:ok = :inet.setopts(sock, packet: 4)
{:keep_state, %__MODULE__{data | sid: nil, sslsock: nil, sock: sock}, {:reply, from, :ok}}
end
def handle_event({:call, from}, {:start_service, service, opts}, _, %__MODULE__{} = data) do
if data.sid do
keys = if Keyword.get(opts, :escrow, false), do: ["EscrowBag"], else: []
args = Map.merge(%{"Service" => service}, Map.take(data.prec, keys))
case Services.rpc(sock(data), "StartService", args) do
{:ok, %{"Service" => ^service, "EnableServiceSSL" => ssl?, "Port" => port}} ->
{:keep_state_and_data, {:reply, from, {:ok, %{port: port, ssl: ssl?}}}}
{:ok, %{"Error" => error}} ->
{:keep_state_and_data, {:reply, from, {:error, error}}}
error ->
Logger.error("#{__MODULE__}: Unexpected response from start service: #{inspect(error)}")
{:keep_state_and_data, {:reply, from, {:error, :failed}}}
end
else
{:keep_state_and_data, {:reply, from, {:error, :no_session}}}
end
end
def handle_event(:info, {:DOWN, ref, _, _, reason}, _, %__MODULE__{mref: ref}) do
{:stop, reason}
end
defp maybe_get_pair_record(muxd, udid) do
case Muxd.get_pair_record(muxd, udid) do
{:ok, prec} ->
{:ok, prec}
_ ->
{:ok, nil}
end
end
defp sock(%__MODULE__{sock: sock, sslsock: sslsock}), do: if(sslsock, do: sslsock, else: sock)
end