Packages

This is an implementation of Mark Qvist's Reticulum Network Stack, a 'cryptography-based networking stack for building local and wide-area networks with readily available hardware.'

Current section

Files

Jump to
reticulum lib rns packet path_request.ex
Raw

lib/rns/packet/path_request.ex

defmodule RNS.Packet.PathRequest do
@moduledoc """
A path request packet is a packet that is sent to request an `RNS.Packet.Announce` of a specific `RNS.Destination`.
"""
@type t() :: RNS.Packet.t()
@type tag() :: binary()
require Record
require Logger
@spec new(RNS.Destination.hash(), RNS.Crypto.truncated_hash()) :: t()
def new(destination_hash, tag \\ RNS.Identity.random_hash()) do
# TODO: If transport add transport destination between destination hash and tag
data = <<destination_hash::binary, tag::binary>>
request_destination_hash =
destination()
|> RNS.Destination.hash()
RNS.Packet.new(data, request_destination_hash, destination_type: :plain)
end
@spec destination() :: RNS.Destination.t()
defp destination() do
RNS.Destination.new(app_name: "rnstransport", aspects: ["path", "request"], type: :plain)
end
@doc """
Requests a path and waits for the response.
The timeout is in seconds and it's default value is 15.
The same process will wait for the response, so the process should not be a server supposed to receive messages!
If a message that is not the expected destination, then `{:error, :invalid_message}` will be returned.
"""
@spec wait_request(RNS.Destination.hash()) ::
{:ok, RNS.Destination.t()} | {:error, :timeout | :invalid_message}
def wait_request(destination_hash, timeout \\ 15) do
tag = RNS.Identity.random_hash()
packet = new(destination_hash, tag)
RNS.AnnounceHashStore.add_handler(destination_hash, nil)
packet
|> RNS.Interface.send()
receive do
{:announce, destination} ->
RNS.AnnounceHashStore.remove_handler(destination_hash, nil)
{:ok, destination}
_ ->
RNS.AnnounceHashStore.remove_handler(destination_hash, nil)
{:error, :invalid_message}
after
timeout * 1000 ->
RNS.AnnounceHashStore.remove_handler(destination_hash, nil)
{:error, :timeout}
end
end
# This is the part for the path request handler.
@doc """
Handles inbound path requests.
This is spawned(new process!) by `RNS.PathResponse`.
"""
@spec handle(RNS.Packet.data(), RNS.Config.transport(), RNS.Interface.record()) :: :ok
def handle(data, transport, source) do
{destination_hash, unique_tag} =
case data do
<<destination_hash::binary-size(16), _transport_id::binary-size(16), tag::binary>>
when byte_size(tag) > 0 ->
{destination_hash, <<destination_hash::binary, tag::binary>>}
<<destination_hash::binary-size(16), tag::binary>> ->
{destination_hash, <<destination_hash::binary, tag::binary>>}
end
Logger.debug(
"Received path request from #{inspect(source)} for destination #{RNS.Destination.to_string(destination_hash)}."
)
if not RNS.PathRequestStore.exists?(unique_tag) do
case RNS.DestinationStore.fetch(destination_hash) do
{:ok, destination} ->
RNS.PathRequestStore.add(unique_tag)
maybe_answer(destination, source, transport)
{:error, :not_found} ->
:ok
end
end
end
@spec maybe_answer(RNS.Destination.t(), RNS.Interface.t(), RNS.Config.transport()) :: :ok
def maybe_answer(destination, source, transport) do
case RNS.Destination.hops(destination) do
0 ->
answer(destination, source)
:ok
_ ->
if transport do
answer(destination, source)
end
end
end
@spec answer(RNS.Destination.t(), RNS.Interface.record()) :: :ok
def answer(destination, interface) when Record.is_record(destination, :destination) do
case RNS.Destination.announce(destination) do
nil -> :ok
announce -> RNS.Interface.send(announce, interface)
end
end
# Path request handler.
use RNS.GenDestination
@spec start_link(RNS.Config.transport()) :: {:ok, pid()}
def start_link(transport) do
RNS.GenDestination.start_link(__MODULE__, destination(), transport)
end
@impl true
def init(transport) do
{:ok, transport}
end
@impl true
def handle_data(data, source, transport) do
spawn(RNS.Packet.PathRequest, :handle, [data, transport, source])
{:ok, transport}
end
end