Packages

Deal with bittorrent tracker requests and responses

Current section

Files

Jump to
tracker_request lib tracker_request.ex
Raw

lib/tracker_request.ex

defmodule TrackerRequest do
@flag_compact "true"
@spec remaining(Map.t) :: Integer
defp remaining(torrent) do
if "length" in Map.keys torrent do
torrent["info"]["length"]
else
Enum.reduce torrent["info"]["files"], 0, fn (f, acc) ->
acc + f["length"]
end
end
end
def sha_sum(binary) do
:crypto.hash(:sha, binary)
end
@spec info_hash(Map.t) :: binary
defp info_hash(torrent) do
torrent["info"] |> Bencoder.encode |> sha_sum
end
@spec generate_query(Map.t, Keyword.t) :: String
defp generate_query(torrent, client) do
q = %{
"port" => to_string(client[:listening_port]),
"info_hash" => client[:info_hash],
"uploaded" => 0,
"event" => "started",
"left" => to_string(remaining(torrent)),
"downloaded" => 0,
"compact" => @flag_compact,
"peer_id" => client[:peer_id]
} |> URI.encode_query
s = "#{torrent["announce"]}?#{q}"
IO.puts s
s
end
@spec request(Map.t, Keyword.t) :: { :ok, Keyword.t } | { :error, term }
def request(torrent, [listening_port: listening_port, peer_id: peer_id]) do
client = [peer_id: peer_id, info_hash: info_hash(torrent), listening_port: listening_port]
{status, body} = torrent |> generate_query(client) |> String.to_char_list |> get_body
if status == :ok do
data = Bencoder.decode(body)
if Map.has_key?(data, "failure reason") do
{:error, String.to_atom(data["failure reason"])}
else
client = data
|> Map.put("info_hash", client[:info_hash])
|> Map.update!("peers", &parse_peer_response/1)
{:ok, client}
end
else
{:error, body}
end
end
@spec get_body(binary) :: {:ok, binary} | {:error, term}
defp get_body(query) do
{ ok, {_, _, body} } = :httpc.request(:get, { query, []}, [], [])
if ok == :ok do
{:ok, body |> :binary.list_to_bin}
else
{:error, :http}
end
end
defp parse_peer_response(data) when is_binary(data) do
Enum.chunk(data, 6).map(&analyze/1)
end
defp parse_peer_response(data) when is_list(data) do
Enum.map data, fn (a) ->
%Peer{ ip: String.to_char_list(a["ip"]), port: a["port"], peer_id: Map.get(a, "peer id", "") }
end
end
defp analyze(data) do
<< ip0 :: 8-integer-big-unsigned, ip1 :: 8-integer-big-unsigned,
ip2 :: 8-integer-big-unsigned, ip3 :: 8-integer-big-unsigned,
port :: 16-integer-big-unsigned >> = data
%Peer{ ip: "#{ip0}.#{ip1}.#{ip2}.#{ip3}", port: port, peer_id: nil }
end
end