Current section
Files
Jump to
Current section
Files
lib/codec.ex
defmodule Multiaddr.Codec do
@moduledoc false
import Multiaddr.Utils.Varint
import Multiaddr.Utils
alias Multiaddr.Protocol, as: Prot
defp list_protocols(bytes, protocols) when bytes == <<>> do
{:ok, protocols}
end
defp list_protocols(bytes, protocols) when is_binary(bytes) do
case read_raw_protocol(bytes) do
{:ok, {next_index, protocol, _raw_value}} ->
list_protocols(split_binary(bytes, next_index..-1), protocols ++ [protocol])
error ->
error
end
end
def list_protocols(bytes) when is_binary(bytes) do
list_protocols(bytes, [])
end
def validate_bytes(bytes) when bytes == <<>> do
{:ok, bytes}
end
def validate_bytes(bytes) when is_binary(bytes) do
case read_raw_protocol(bytes) do
{:ok, {next_index, _protocol, _raw_value}} ->
validate_bytes(split_binary(bytes, next_index..-1))
error ->
error
end
end
def string_to_bytes(string) when is_binary(string) do
string = String.trim_trailing(string, "/")
split_string = String.split(string, "/")
case Enum.fetch(split_string, 0) do
{:ok, ""} ->
string_to_bytes(Enum.slice(split_string, 1..-1), <<>>)
{:ok, first_string} ->
{:error, {:invalid_string, "Address first character must be '/' not '#{first_string}'"}}
end
end
defp string_to_bytes(string_split, bytes) when string_split == [] and bytes == "" do
{:error, {:invalid_string, "Invalid empty address string"}}
end
defp string_to_bytes(string_split, bytes) when string_split == [] and is_binary(bytes) do
{:ok, bytes}
end
defp string_to_bytes(string_split, bytes) when is_list(string_split) and is_binary(bytes) do
with {:ok, protocol_name} <- Enum.fetch(string_split, 0),
{:ok, protocol} <- Map.fetch(Prot.protocols_by_name(), protocol_name),
string_split <- Enum.slice(string_split, 1..-1),
{:ok, {next_index, protocol_bytes}} <- get_protocol_value(protocol, string_split) do
bytes = bytes <> protocol.vcode <> protocol_bytes
string_to_bytes(Enum.slice(string_split, next_index..-1), bytes)
else
:error -> {:error, {:invalid_protocol_name, Enum.fetch(string_split, 0)}}
error -> error
end
end
def bytes_to_string(bytes) when is_binary(bytes) do
bytes_to_string(bytes, "")
end
defp bytes_to_string(bytes, string) when bytes == <<>> and string == "" do
{:error, {:invalid_bytes, "Invalid empty address bytes"}}
end
defp bytes_to_string(bytes, string) when bytes == <<>> and is_binary(string) do
{:ok, string}
end
defp bytes_to_string(bytes, string) when is_binary(bytes) and is_binary(string) do
with {:ok, {next_index, protocol, value}} <- read_protocol(bytes) do
string =
if protocol.size == 0 do
string <> "/" <> protocol.name
else
string <> "/" <> protocol.name <> "/" <> value
end
bytes_to_string(split_binary(bytes, next_index..-1), string)
end
end
def find_sub_multiaddr(maddr_bytes, sub_maddr_bytes)
when is_binary(maddr_bytes) and is_binary(sub_maddr_bytes) do
find_sub_multiaddr(maddr_bytes, sub_maddr_bytes, 0)
end
defp find_sub_multiaddr(maddr_bytes, sub_maddr_bytes, index)
when is_binary(maddr_bytes) and is_binary(sub_maddr_bytes) and is_integer(index) do
if maddr_bytes == sub_maddr_bytes do
{:ok, index}
else
case read_raw_protocol(maddr_bytes) do
{:ok, {next_index, _protocol, _raw_value}} ->
maddr_bytes = split_binary(maddr_bytes, next_index..-1)
find_sub_multiaddr(maddr_bytes, sub_maddr_bytes, next_index)
error = {:error, {:not_found, _value}} ->
error
{:error, reason} ->
raise Multiaddr.Error, reason: reason
end
end
end
defp find_sub_multiaddr(maddr_bytes, sub_maddr_bytes, _index) when maddr_bytes == "" do
if {:ok, _bytes} = validate_bytes(sub_maddr_bytes) do
{:error, {:not_found, "Not found encapsulated multiaddr"}}
else
{:eror, {:invalid_bytes, "Invalid encapsulated Multiaddr bytes"}}
end
end
def find_protocol(bytes, %Prot{} = protocol) when is_binary(bytes) do
find_protocol(bytes, protocol.code, 0)
end
defp find_protocol(bytes, code, _index) when bytes == "" do
if {:ok, protocol} = Map.fetch(Prot.protocols_by_code(), code) do
{:error, {:not_found, "Not found protocol #{protocol.name}"}}
else
{:eror, {:invalid_protocol_code, "Invalid protocol code #{code}"}}
end
end
defp find_protocol(bytes, code, index)
when is_binary(bytes) and is_integer(code) and is_integer(index) do
with {:ok, {next_index, protocol, value}} <- read_protocol(bytes) do
if protocol.code == code do
{:ok, index, value}
else
find_protocol(split_binary(bytes, next_index..-1), code, index + next_index)
end
else
# If fails, Multiaddr bytes are corrupted
error = {:error, {:not_found, _reason}} -> error
{:error, reason} -> raise Multiaddr.Error, reason: reason
error -> raise Multiaddr.Error, reason: {:unknown, "Unknown error: #{inspect(error)}"}
end
end
defp read_protocol(bytes) when is_binary(bytes) do
with {:ok, {next_index, protocol, raw_value}} <- read_raw_protocol(bytes) do
value =
if protocol.size > 0 do
protocol.transcoder.bytes_to_string.(raw_value)
else
{:ok, raw_value}
end
case value do
{:ok, protocol_value} -> {:ok, {next_index, protocol, protocol_value}}
# It shouldn't fail because read_raw_protcol validates bytes. If it does, need to check transcoders
error -> error
end
end
end
defp read_raw_protocol(bytes) when bytes == <<>> do
{:error, {:invalid_bytes, "Tried to read empty bytes"}}
end
defp read_raw_protocol(bytes) when is_binary(bytes) do
{:ok, {value_index, code}} = read_varint(bytes)
bytes = split_binary(bytes, value_index..-1)
with {:ok, protocol} <- Map.fetch(Prot.protocols_by_code(), code),
{:ok, {next_index, size}} <- size_for_protocol(protocol, bytes),
true <- byte_size(bytes) >= size,
bytes = split_binary(bytes, next_index..-1),
{:ok, protocol_bytes} = read_raw_protocol_value(protocol, bytes, size) do
{:ok, {value_index + next_index + size, protocol, protocol_bytes}}
else
:error ->
{:error, {:invalid_protocol_code, code}}
false ->
{:error,
{:invalid_bytes,
"Invalid protocol value: too short value for protocol #{
Map.fetch(Prot.protocols_by_code(), code).name
}"}}
error ->
error
end
end
defp read_raw_protocol_value(_protocol, bytes, size) when is_binary(bytes) and size == 0 do
{:ok, ""}
end
defp read_raw_protocol_value(protocol, bytes, size)
when is_binary(bytes) and is_integer(size) and size > 0 and byte_size(bytes) >= size do
protocol_bytes = split_binary(bytes, 0..(size - 1))
case protocol.transcoder.validate_bytes.(protocol_bytes) do
{:ok, _bytes} -> {:ok, protocol_bytes}
error -> error
end
end
defp get_protocol_value(%Prot{size: size} = _protocol, string_split)
when size == 0 and is_list(string_split) do
{:ok, {0, ""}}
end
defp get_protocol_value(%Prot{size: size, path: is_path?} = protocol, string_split)
when size == :prefixed_var_size do
{next_index, value_string} =
if is_path? do
{length(string_split), Enum.join(string_split, "/")}
else
{1, Enum.at(string_split, 0, "")}
end
with {:ok, protocol_bytes} <- protocol.transcoder.string_to_bytes.(value_string) do
protocol_bytes = Varint.LEB128.encode(byte_size(protocol_bytes)) <> protocol_bytes
{:ok, {next_index, protocol_bytes}}
end
end
defp get_protocol_value(%Prot{size: size} = protocol, string_split) when size > 0 do
with {:ok, protocol_bytes} <-
protocol.transcoder.string_to_bytes.(Enum.at(string_split, 0, "")) do
{:ok, {1, protocol_bytes}}
end
end
end