Current section
Files
Jump to
Current section
Files
lib/rns/gen_destination.ex
defmodule RNS.GenDestination do
# TODO: support timeouts.
@moduledoc """
This module is used to create processes listening for packets and link requests on a destination.
Packet -> run function
Link request -> start another process for link
"""
# behaviour-related stuff
# NOTE: 'child' means the module using RNS.GenDestination.
@type child_state() :: any()
@type stop_reason() :: any()
@type opts() :: [
announce_on_start: boolean(),
announce_interval: integer()
]
@type state() :: tuple()
# The init function, called by start_link
@callback init(any()) ::
{:ok, child_state()}
| {:ok, child_state(), timeout()}
| {:stop, stop_reason()}
@callback handle_data(RNS.Packet.data(), RNS.Interface.t(), child_state()) ::
{:ok, child_state()}
| {:ok, child_state(), timeout()}
| {:stop, stop_reason()}
# TODO: handle link requests, if they are accepted a new process should be created, running some other callbacks.
# @callback handle_link_request()
# This is so `use RNS.GenDestination` automatically adds the behaviour and childspecs.
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
@behaviour RNS.GenDestination
def child_spec(init_arg) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [init_arg]}
}
end
defoverridable child_spec: 1
# TODO: implement some functions for this behaviour, to make some functions optional.
end
end
# process-related stuff
require Logger
require Record
Record.defrecord(:state,
module: nil,
destination: nil,
child_state: nil,
opts: []
)
@spec start_link(atom(), RNS.Destination.t(), any(), opts()) :: {:ok, pid()} | {:error, any()}
def start_link(module, destination, child_opts, opts \\ []), do: GenServer.start_link(__MODULE__, {module, destination, child_opts, opts})
@spec start(atom(), RNS.Destination.t(), any(), opts()) :: {:ok, pid()} | {:error, any()}
def start(module, destination, child_opts, opts \\ []), do: GenServer.start(__MODULE__, {module, destination, child_opts, opts})
use GenServer
@impl true
@doc false
@spec init({atom(), RNS.Destination.t(), any(), opts()}) :: {:stop, stop_reason()} | {:ok, state(), timeout()}
def init({module, destination, child_opts, opts}) do
# run the 'child' module's init function.
case module.init(child_opts) do
{:ok, child_state} ->
do_init(module, destination, child_state, opts, :infinity)
{:ok, child_state, timeout} when is_integer(timeout) ->
do_init(module, destination, child_state, opts, timeout)
{:stop, reason} ->
{:stop, reason}
end
end
@spec do_init(atom(), RNS.Destination.t(), child_state(), opts(), timeout()) :: {:ok, state(), timeout()}
defp do_init(module, destination, child_state, opts, child_timeout) do
interface = RNS.Interface.new("GenDestination##{RNS.Destination.to_string(destination)}", self(), virtual: true)
RNS.InterfaceManager.interface_started(interface)
destination = RNS.Destination.change_interface(destination, interface)
RNS.DestinationStore.put(destination)
if Keyword.get(opts, :announce_on_start, true) do
RNS.Destination.broadcast_announce(destination, update_announce: true)
end
state = state(
module: module,
destination: destination,
child_state: child_state,
opts: opts
)
{:ok, state, shortest_timeout(state, child_timeout)}
end
@spec shortest_timeout(state(), timeout()) :: timeout()
defp shortest_timeout(state, _child_timeout) do
# TODO: get the smallest of all the different timeouts(child, announce)
opts = state(state, :opts)
Keyword.get(opts, :announce_interval, :infinity)
end
@impl true
def handle_info({:inbound, packet, source}, state) do
case RNS.Packet.packet_type(packet) do
:data ->
handle_data_packet(packet, source, state)
end
end
@spec handle_data_packet(RNS.Packet.t(), RNS.Interface.t(), state()) :: {:noreply, state()}
defp handle_data_packet(packet, source, state) do
destination = state(state, :destination)
identity = destination
|> RNS.Destination.identity()
packet_destination_type = RNS.Packet.destination_type(packet)
destination_type = RNS.Destination.type(destination)
case packet_destination_type do
:single when destination_type == :single ->
case RNS.Packet.decrypt(packet, identity) do
{:ok, packet} ->
# TODO: allow handle_data to decide whether to prove or not(:prove/:noprove instead of :ok).
RNS.Interface.send(RNS.Packet.DataPacketProof.new(RNS.Packet.hash(packet), identity), source)
handle_decrypted_data(RNS.Packet.data(packet), source, state)
{:error, reason} ->
Logger.warning("(RNS.GenDestination) Failed to decrypt data packet to destination #{RNS.Destination.to_string(destination)}, error: #{inspect(reason)}.")
{:noreply, state}
end
:plain when destination_type == :plain ->
handle_decrypted_data(RNS.Packet.data(packet), source, state)
destination_type ->
Logger.warning("(RNS.GenDestination) Received packet with unsupported destination type: #{destination_type}.")
end
end
@spec handle_decrypted_data(RNS.Packet.data(), RNS.Interface.t(), state()) :: {:noreply, state()}
defp handle_decrypted_data(data, source, state) do
child_return =
state(state, :module).handle_data(
data,
source,
state(state, :child_state)
)
case child_return do
{:ok, new_child_state} ->
{:noreply, state(state, child_state: new_child_state)}
{:stop, reason} ->
{:stop, reason}
end
end
end