defmodule MTProto.Session.Brain do alias MTProto.{Auth, Session} alias MTProto.Session.{Handler, History} require Logger @moduledoc false # Process plain messages def process(msg, session_id, :plain) do name = Map.get(msg, :name) case name do "resPQ" -> Auth.resPQ(msg, session_id) "server_DH_params_ok" -> Auth.server_DH_params_ok(msg, session_id) "server_DH_params_fail" -> Auth.server_DH_params_fail(msg, session_id) "dh_gen_ok" -> Auth.dh_gen_ok(msg, session_id) "dh_gen_fail" -> Auth.dh_gen_fail(msg, session_id) "dh_gen_retry" -> Auth.dh_gen_fail(msg, session_id) "error" -> handle_error(session_id, msg) _ -> Logger.debug "[MT][Brain] Unknow predicate : #{name}" end end # Process encrypted messages def process(msg, session_id, :encrypted) do session = Session.get(session_id) name = Map.get(msg, :name) # Process RPC case name do "rpc_result" -> req_msg_id = Map.get(msg, :req_msg_id) result = Map.get msg, :result name = result |> Map.get(:name) case name do "auth.sentCode" -> hash = Map.get result, :phone_code_hash Session.update(session_id, phone_code_hash: hash) "auth.authorization" -> Logger.debug "Session #{session_id} is now logged in !" user_id = Map.get(result, :user) |> Map.get(:id) Session.update(session_id, user_id: user_id) "rpc_error" -> handle_rpc_error(session_id, result) _ -> :noop end # Remove req_msg_id from 'sent' queue History.drop session_id, req_msg_id # ACK msg_ids = [Map.get(msg, :msg_id)] ack = MTProto.Method.msgs_ack(msg_ids) Handler.send_encrypted(ack, session_id) "bad_msg_notification" -> bad_msg_id = Map.get(msg, :bad_msg_id) error_code = Map.get(msg, :error_code) case error_code do 32 -> Logger.warn "msg_seqno too low : #{msg.bad_msg_id}" _ -> :noop end retry(session_id, bad_msg_id) "bad_server_salt" -> new_server_salt = Map.get(msg, :new_server_salt) bad_msg_id = Map.get(msg, :bad_msg_id) # Note : store server_salt in serialized 'long' (little endian) in order # to avoid endianess hell Session.update session_id, server_salt: TL.serialize(new_server_salt, :long) retry(session_id, bad_msg_id) _ -> :noop end # Notify the client if session.client != nil do send session.client, {:tg, session_id, msg} else IO.puts "No client for #{session_id}, printing to console." IO.inspect {session_id, msg}, limit: :infinity end end ## Error Handling ## See https://core.telegram.org/api/errors # Handle errors for plain messages defp handle_error(session_id, msg) do error_code = Map.get(msg, :code) case error_code do -404 -> session = Session.get(session_id) if session.auth_key == <<0::8*8>> do Logger.debug "[MT][Brain] I received a -404 error. I still don't have an auth key for this DC (#{session.dc}) so I'm going to generate one ! I'm a workaround ;(" Auth.req_pq(session_id) end _ -> Logger.warn "[MT][Brain] Unknown error : #{error_code}" end end # Handle errors for encrypted messages defp handle_rpc_error(_session_id, rpc_result) do error_code = Map.get(rpc_result, :error_code) error_message = Map.get(rpc_result, :error_message) Logger.warn "[MT][Brain] RPC error : #{error_code} | #{error_message}" case error_code do _ -> :noop end end def retry(session_id, bad_msg_id) do # Get 'bad' message content result = History.get(session_id, bad_msg_id) unless result do Logger.warn("Error for message #{bad_msg_id} but not found in session history !") else {retry_count, content} = result History.drop session_id, bad_msg_id # Remove existing message from history if retry_count > 0 do # Resend {:ok, new_msg_id} = Handler.send_encrypted(content, session_id) History.put session_id, new_msg_id, {retry_count - 1, content} end end end end