Packages

A simple kademlia implementation

Current section

Files

Jump to
exkad lib buckets.ex
Raw

lib/buckets.ex

defmodule Exkad.Node.Buckets do
use GenServer
use Bitwise
require Logger
alias Exkad.Node
alias Exkad.Node.Descriptor, as: ND
@k 10
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
def k do
@k
end
def init(%{node: node, parent: parent} = options) do
buckets = node.id
|> Node.id_to_bin
|> Enum.map(fn _ -> [] end)
Exkad.Node.register(parent, :buckets, self)
Logger.debug("Buckets have been started in #{inspect self}")
{:ok, %{buckets: buckets, node: node}}
end
def dump(buckets) do
GenServer.call(buckets, :dump)
end
def add(buckets, node) do
Logger.debug("Adding #{node} to buckets of #{inspect self}")
GenServer.call(buckets, {:add, node})
end
def add_all(buckets, nodes) do
# Enum.reduce(nodes, state, fn(desc, acc) ->
# add_to_bucket(acc, desc)
# end)
Enum.map(nodes, fn node -> add(buckets, node) end)
end
def k_closest(buckets, to_id) do
GenServer.call(buckets, {:k_closest, to_id})
end
def prefix_length([a | this_rest], [a | other_rest], acc) do
prefix_length(this_rest, other_rest, acc + 1)
end
def prefix_length(_, _, acc), do: acc
def prefix_length(this, other) do
prefix_length(Node.id_to_bin(this), Node.id_to_bin(other), 0)
end
#
# xor distance between from_id and to_id
#
def distance_from(from_id, to_id) do
Enum.zip(Node.id_to_bin(from_id), Node.id_to_bin(to_id))
|> Enum.map(fn {f, t} -> f ^^^ t end)
end
#
# Get the k_closest_nodes to `to_id` in local buckets
#
def k_closest_nodes_to(nodes, to_id) do
nodes
|> Enum.uniq
|> Enum.map(fn %ND{id: id, loc: loc} = nd -> {distance_from(id, to_id), nd} end)
|> Enum.sort(fn {a, _}, {b, _} -> a <= b end)
|> Enum.take(@k)
|> Enum.map(fn {_distance, desc} -> desc end)
end
def handle_call({:add, %ND{id: id}}, _from, %{node: %ND{id: id}} = state) do
{:reply, :ok, state}
end
def handle_call({:add, add_node}, _from, %{node: node, buckets: buckets} = state) do
%ND{id: add_id} = add_node
index = prefix_length(node.id, add_id)
bucket = [add_node | Enum.at(buckets, index)] |> Enum.uniq |> Enum.take(@k)
buckets = List.replace_at(buckets, index, bucket)
{:reply, :ok, Enum.into(%{buckets: buckets}, state)}
end
def handle_call({:k_closest, to_id}, _from, %{buckets: buckets} = state) do
k_closest = List.flatten(buckets) |> k_closest_nodes_to(to_id)
{:reply, k_closest, state}
end
def handle_call(:dump, _from, %{buckets: buckets} = state) do
{:reply, buckets, state}
end
end