Current section
Files
Jump to
Current section
Files
lib/multiaddr.ex
defmodule Multiaddr do
@moduledoc """
This is the Multiaddr public API module.
"""
import Multiaddr.Utils
alias Multiaddr.Protocol
alias Multiaddr.Codec
alias Multiaddr.Error
defstruct [:bytes]
@type protocol_name() :: String.t()
@type protocol_value() :: String.t()
@type protocol_code() :: integer()
@doc """
Parses and validates an input string, returning a `Multiaddr`.
Returns: `{:ok, maddr}`
## Examples
iex> {:ok, _maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
"""
@spec new_multiaddr_from_string(String.t()) :: %Multiaddr{}
def new_multiaddr_from_string(string) when is_binary(string) do
case Codec.string_to_bytes(string) do
{:ok, bytes} -> {:ok, %Multiaddr{bytes: bytes}}
error -> error
end
end
@doc """
Initializes a Multiaddr from a byte representation. It validates it as an input string.
Returns: `{:ok, maddr}`
## Examples
iex> {:ok, _maddr} = Multiaddr.new_multiaddr_from_bytes(<<4, 127, 0, 0, 1>>)
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
"""
@spec new_multiaddr_from_bytes(binary()) :: %Multiaddr{}
def new_multiaddr_from_bytes(bytes) when is_binary(bytes) do
case Codec.validate_bytes(bytes) do
{:ok, _bytes} -> {:ok, %Multiaddr{bytes: bytes}}
error -> error
end
end
@doc """
Tests whether two multiaddrs are equal.
Returns: `true` or `false`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> true = Multiaddr.equal(maddr, maddr)
true
"""
@spec equal(%Multiaddr{}, %Multiaddr{}) :: boolean()
def equal(%Multiaddr{} = maddr1, %Multiaddr{} = maddr2) do
maddr1 == maddr2
end
@doc """
Returns the byte representation of this Multiaddr.
Returns: `maddr_bytes`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> Multiaddr.bytes(maddr)
<<4, 127, 0, 0, 1>>
"""
@spec bytes(%Multiaddr{}) :: binary()
def bytes(%Multiaddr{bytes: maddr_bytes} = _maddr) do
maddr_bytes
end
@doc """
Returns the string representation of this Multiaddr.
Returns: `maddr_string`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> Multiaddr.string(maddr)
"/ip4/127.0.0.1"
"""
@spec string(%Multiaddr{}) :: String.t()
def string(%Multiaddr{bytes: maddr_bytes} = _maddr) do
case Codec.bytes_to_string(maddr_bytes) do
{:ok, string} -> string
{:error, reason} -> raise Multiaddr.Error, reason: reason
end
end
@doc """
Returns the list of protocols this Multiaddr has.
Returns: `protocols_list`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> [ip4_protocol] = Multiaddr.protocols(maddr)
iex> ip4_protocol.name == "ip4"
true
"""
@spec protocols(%Multiaddr{}) :: list(%Protocol{})
def protocols(%Multiaddr{bytes: maddr_bytes} = _maddr) do
case Codec.list_protocols(maddr_bytes) do
{:ok, maddr_protocols} -> maddr_protocols
{:error, reason} -> raise Multiaddr.Error, reason: reason
end
end
@doc """
It wraps a given Multiaddr, returning the resulting joined Multiaddr.
Returns: `{:ok, maddr}`
## Examples
iex> {:ok, maddr_1} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> {:ok, maddr_2} = Multiaddr.new_multiaddr_from_string("/tcp/80")
{:ok, %Multiaddr{bytes: <<6, 0, 80>>}}
iex> {:ok, _maddr} = Multiaddr.encapsulate(maddr_1, maddr_2)
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1, 6, 0, 80>>}}
"""
@spec encapsulate(%Multiaddr{}, %Multiaddr{}) :: %Multiaddr{}
def encapsulate(
%Multiaddr{bytes: maddr_1_bytes} = maddr_1,
%Multiaddr{bytes: maddr_2_bytes} = _maddr_2
) do
with false <- contains_path_protocol?(maddr_1),
{:ok, _bytes} <- Codec.validate_bytes(maddr_1_bytes),
{:ok, _bytes} <- Codec.validate_bytes(maddr_2_bytes) do
{:ok, %Multiaddr{bytes: maddr_1_bytes <> maddr_2_bytes}}
else
true -> {:error, {:invalid_order, "Path must be last protocol"}}
{:error, reason} -> raise Multiaddr.Error, reason: reason
end
end
defp contains_path_protocol?(%Multiaddr{} = maddr) do
maddr
|> protocols()
|> Enum.any?(fn p -> p.path end)
end
@doc """
It unwraps Multiaddr up until the given Multiaddr is found.
Returns: `{:ok, maddr}`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1/tcp/80")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1, 6, 0, 80>>}}
iex> {:ok, maddr_2} = Multiaddr.new_multiaddr_from_string("/tcp/80")
{:ok, %Multiaddr{bytes: <<6, 0, 80>>}}
iex> {:ok, maddr_1} = Multiaddr.decapsulate(maddr, maddr_2)
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1>>}}
iex> Multiaddr.string(maddr_1)
"/ip4/127.0.0.1"
"""
@spec decapsulate(%Multiaddr{}, %Multiaddr{}) :: %Multiaddr{}
def decapsulate(
%Multiaddr{bytes: maddr_1_bytes} = _maddr_1,
%Multiaddr{bytes: maddr_2_bytes} = _maddr_2
) do
with {:ok, index} = Codec.find_sub_multiaddr(maddr_1_bytes, maddr_2_bytes) do
{:ok, %Multiaddr{bytes: split_binary(maddr_1_bytes, 0..(index - 1))}}
end
end
@doc """
It returns the value in string representation, for a given protocol. If there are more than one, only the first is returned.
Returns: `{:ok, value}`
## Examples
iex> {:ok, maddr} = Multiaddr.new_multiaddr_from_string("/ip4/127.0.0.1/tcp/80")
{:ok, %Multiaddr{bytes: <<4, 127, 0, 0, 1, 6, 0, 80>>}}
iex> Multiaddr.value_for_protocol(maddr, "tcp")
{:ok, "80"}
"""
@spec value_for_protocol(%Multiaddr{}, protocol_name()) :: protocol_value()
def value_for_protocol(%Multiaddr{bytes: maddr_bytes} = _maddr, name) when is_binary(name) do
with {:ok, protocol} <- Map.fetch(Protocol.protocols_by_name(), name),
{:ok, _index, value} <- Codec.find_protocol(maddr_bytes, protocol) do
{:ok, value}
else
:error -> {:error, {:invalid_protocol_name, name}}
error -> error
end
end
@spec value_for_protocol(%Multiaddr{}, protocol_code()) :: protocol_value()
def value_for_protocol(%Multiaddr{} = maddr, code) when is_integer(code) do
with {:ok, protocol} <- Map.fetch(Protocol.protocols_by_code(), code),
{:ok, _index, value} <- Codec.find_protocol(maddr.bytes, protocol) do
{:ok, value}
else
:error -> {:error, {:invalid_protocol_code, code}}
error -> error
end
end
def format_error(error), do: Error.format_error(error)
end