Current section
Files
Jump to
Current section
Files
lib/rns/destination_store.ex
defmodule RNS.DestinationStore do
@moduledoc """
Manages an ets table that holds the paths to destinations(including local ones).
"""
require Record
def child_spec(_args) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, []}
}
end
def start_link() do
pid = spawn_link(__MODULE__, :init, [])
{:ok, pid}
end
# Client
@doc "Find a destination in the store using its hash."
@spec fetch(RNS.Destination.hash()) :: {:ok, RNS.Destination.t()} | {:error, :not_found}
def fetch(destination_hash) do
case :ets.lookup(:destinations, destination_hash) do
[result] ->
if RNS.Destination.expired?(result) do
delete(destination_hash)
{:error, :not_found}
else
{:ok, result}
end
[] ->
{:error, :not_found}
end
end
@doc "Put a destination into the store."
@spec put(RNS.Destination.t()) :: true
def put(destination) do
:ets.insert(:destinations, destination)
end
@doc "Delete a destination from the store using its hash."
@spec delete(RNS.Destination.hash()) :: true
def delete(destination_hash) do
:ets.delete(:destinations, destination_hash)
end
@doc "Put a ratchet into the store."
@spec put_ratchet(RNS.Identity.ratchet(), RNS.Destination.hash()) :: true
def put_ratchet(ratchet, destination_hash) do
:ets.insert(
:ratchets,
{destination_hash, ratchet, NaiveDateTime.add(NaiveDateTime.utc_now(), 30, :day)}
)
end
@doc "Fetch all ratchets from the store."
@spec fetch_ratchets(RNS.Destination.hash()) :: [RNS.Identity.ratchet()]
def fetch_ratchets(destination_hash) do
ratchets = :ets.lookup(:ratchets, destination_hash)
Enum.reduce(ratchets, [], fn ratchet, ratchets ->
if RNS.Destination.ratchet_expired?(ratchet) do
delete_ratchet(elem(ratchet, 1))
ratchets
else
[elem(ratchet, 1) | ratchets]
end
end)
end
@doc "Fetch the most recent ratchet from the store."
@spec fetch_recent_ratchet(RNS.Destination.hash()) ::
{:ok, RNS.Identity.ratchet()} | {:error, :not_found}
def fetch_recent_ratchet(destination_hash) do
ratchets = :ets.lookup(:ratchets, destination_hash)
most_recent_ratchet =
Enum.reduce(ratchets, nil, fn ratchet, most_recent_ratchet ->
if RNS.Destination.ratchet_expired?(ratchet) do
delete_ratchet(elem(ratchet, 1))
most_recent_ratchet
else
if most_recent_ratchet == nil or
NaiveDateTime.after?(elem(ratchet, 2), elem(most_recent_ratchet, 2)) do
ratchet
else
most_recent_ratchet
end
end
end)
case most_recent_ratchet do
nil ->
{:error, :not_found}
_ ->
{:ok, elem(most_recent_ratchet, 1)}
end
end
@doc "Delete a ratchet from the store."
@spec delete_ratchet(RNS.Identity.ratchet()) :: true
def delete_ratchet(ratchet) do
:ets.match_delete(:ratchets, {:_, ratchet, :_})
end
# Callbacks
@spec init() :: :ok
def init() do
:ets.new(:destinations, [
{:read_concurrency, true},
{:write_concurrency, true},
{:keypos, RNS.Destination.hash_position() + 1},
:public,
:named_table
])
:ets.new(:ratchets, [
{:read_concurrency, true},
{:write_concurrency, true},
:bag,
:public,
:named_table
])
Process.sleep(:infinity)
end
end