Packages

MTProto (Telegram) implementation for Elixir.

Retired package: Deprecated - This package is not maintained anymore.

Current section

Files

Jump to
telegram_mt lib mtproto session handler.ex
Raw

lib/mtproto/session/handler.ex

defmodule MTProto.Session.Handler do
require Logger
alias MTProto.{TCP, Registry, Crypto, Session, Payload}
alias MTProto.Session.Brain
@moduledoc false
def start_link(session_id, dc_id) do
GenServer.start_link(__MODULE__, {session_id, dc_id}, [])
end
# Initialize the handler
def init({session_id, dc_id}) do
Logger.debug "[Handler] #{session_id} : starting handler."
Registry.set :session, session_id, %Session{handler: self(), dc: dc_id}
{:ok, session_id}
end
# Receive a message, parse and dispatch.
def handle_info({:recv, payload}, session_id) do
session = Registry.get :session, session_id
cond do
# MTProto error message (4 bytes). Do no confuse with RPC errors !
byte_size(payload) == 4 ->
error = :binary.part(payload, 0, 4) |> TL.deserialize(:int)
Brain.process(%{name: "error", code: error}, session_id, :plain)
# Proper messages
byte_size(payload) >= 8 ->
auth_key = :binary.part(payload, 0, 8)
# authorization key composed of 8 <<0>> : plain message.
if auth_key == <<0::8*8>> do
{map, _} = payload |> Payload.parse(:plain)
msg_id = Map.get map, :msg_id
Registry.set :session, session_id, :last_msg_id, msg_id
Brain.process(map, session_id, :plain)
else
# Encrypted message
dc = Registry.get :dc, session.dc
decrypted = payload |> Crypto.decrypt_message(dc.auth_key)
#msg_seqno = :binary.part(decrypted, 24, 4) |> TL.deserialize(:int)
{map, _} = decrypted |> Payload.parse(:encrypted)
msg_id = Map.get map, :msg_id
Registry.set :session, session_id, :last_msg_id, msg_id
Brain.process(map, session_id, :encrypted)
end
true ->
Logger.debug "[Handler] #{session_id} : received unknow message."
end
{:noreply, session_id}
end
# Send a plain message
def handle_info({:send_plain, payload}, session_id) do
send_plain(payload, session_id)
{:noreply, session_id}
end
# Send an encrypted_message
def handle_info({:send, payload}, session_id) do
send_encrypted(payload, session_id)
{:noreply, session_id}
end
def send_plain(payload, session_id) do
session = Registry.get :session, session_id
msg_id = Payload.generate_id
auth_key = 0
packet = TL.serialize(auth_key, :long) <> Payload.wrap(payload, msg_id)
packet |> TCP.wrap(session.seqno) |> TCP.send(session.socket)
# Update the sequence number
Registry.set :session, session_id, :seqno, session.seqno + 1
end
def send_encrypted(payload, session_id) do
session = Registry.get :session, session_id
dc = Registry.get :dc, session.dc
msg_id = Payload.generate_id()
msg_id = if msg_id <= session.last_msg_id do # workaround for issue #2
Logger.warn "Message ID overlap ! Generating with offset..."
Payload.generate_id(1)
else
msg_id
end
# Wrap as encrypted message
msg_seqno = (session.msg_seqno * 2 + 1)
payload = Payload.wrap(payload, msg_id, msg_seqno)
if dc.auth_key != <<0::8*8>> && dc.auth_key != nil do
encrypted_msg = Crypto.encrypt_message(dc.auth_key, dc.server_salt, session_id, payload)
encrypted_msg |> TCP.wrap(session.seqno) |> TCP.send(session.socket)
# Update the sequence numbers
Registry.set(:session, session_id, :msg_seqno, session.msg_seqno + 1)
Registry.set :session, session_id, :seqno, session.seqno + 1
else
{:error, "Auth key does not exist"}
end
end
def terminate(_reason, state) do
Logger.debug "[Handler] #{state} : terminating handler."
{:error, state}
end
end