defmodule ExMobileDevice.Muxd.Socket do @moduledoc false alias ExMobileDevice.Utils alias Version.InvalidVersionError require Logger @protocol_version_plist 1 @msgtype_plist 8 def connect(addr, port) do with {:ok, socket} <- :gen_tcp.connect(addr, port, [:binary, active: false]) do case get_protocol_version(socket, 0) do {:ok, 1} -> {:ok, socket} {:ok, n} -> Logger.error( "#{__MODULE__}: Invalid version when connecting to #{inspect(addr)} port #{port}. Got version #{n}" ) {:error, %InvalidVersionError{version: to_string(n)}} {:error, reason} -> Logger.error( "#{__MODULE__}: Error connecting to #{inspect(addr)} port #{port}. #{inspect(reason)}}" ) {:error, %RuntimeError{message: inspect(reason)}} end end end def close(socket), do: :gen_tcp.close(socket) def request_events(socket, tag \\ 0) do case send_and_recv(socket, %{"MessageType" => "Listen"}, tag) do {:ok, %{"MessageType" => "Result", "Number" => 0}} -> :ok {:ok, %{"MessageType" => "Result", "Number" => _}} = result -> Logger.error("#{__MODULE__}: Error requesting socket events. #{inspect(result)}") {:error, :failed} error -> Logger.error("#{__MODULE__}: Error requesting socket events. #{inspect(error)}") error end end @spec parse_events(binary) :: Enumerable.t() def parse_events(bytes) do Stream.unfold(bytes, fn <> -> {{:parsed, Pealist.decode(msg)}, rest} rest when is_binary(rest) -> {{:partial, rest}, nil} nil -> nil end) end def send_and_recv(socket, request, tag) do with :ok <- send_request(socket, request, tag) do recv_response(socket, tag) end end defp send_request(socket, request, tag) do plist = Map.merge( %{ "ClientVersionString" => "qt4i-usbmuxd", "ProgName" => "exmobiledevice", "kLibUSBMuxVersion" => 3 }, request ) |> Utils.to_plist() header = <<@protocol_version_plist::little-32, @msgtype_plist::little-32, tag::little-32>> request = [<<4 + IO.iodata_length([plist, header])::little-size(32)>>, header, plist] :gen_tcp.send(socket, request) end defp recv_response(socket, tag) when is_integer(tag) do with {:ok, <>} <- :gen_tcp.recv(socket, 4), {:ok, <<1::little-32, 8::little-32, ^tag::little-32, response::binary>>} <- :gen_tcp.recv(socket, size - 4) do {:ok, Pealist.decode(response)} end end defp get_protocol_version(socket, tag) do header = << @protocol_version_plist::little-32, @msgtype_plist::little-32, tag::little-32 >> request = ExMobileDevice.Plist.encode(%{"MessageType" => "ReadBUID"}) msg = [header, request] data = [<<4 + IO.iodata_length(msg)::little-32>> | msg] with :ok <- :gen_tcp.send(socket, data), {:ok, <>} <- :gen_tcp.recv(socket, 16), {:ok, _} <- :gen_tcp.recv(socket, size - 16) do {:ok, vsn} end end end