63 lines
1.6 KiB
Elixir
63 lines
1.6 KiB
Elixir
defmodule Exdns.DnsPacket.Name do
|
|
@moduledoc """
|
|
DNS name encoding and decoding with compression pointers.
|
|
"""
|
|
|
|
import Bitwise
|
|
alias Exdns.DnsPacket.Binary
|
|
|
|
def encode(labels) when is_list(labels) do
|
|
labels
|
|
|> Enum.map(fn label ->
|
|
size = byte_size(label)
|
|
<<size::8, label::binary>>
|
|
end)
|
|
|> IO.iodata_to_binary()
|
|
|> Kernel.<>(<<0>>)
|
|
end
|
|
|
|
def encode(name) when is_binary(name) do
|
|
name
|
|
|> String.trim(".")
|
|
|> String.split(".", trim: true)
|
|
|> encode()
|
|
end
|
|
|
|
def decode(bin, offset) do
|
|
decode(bin, offset, [], MapSet.new())
|
|
end
|
|
|
|
defp decode(bin, offset, labels, visited) do
|
|
if MapSet.member?(visited, offset) do
|
|
{:error, :name_pointer_loop}
|
|
else
|
|
bin
|
|
|> decode_at(offset, labels, MapSet.put(visited, offset))
|
|
end
|
|
end
|
|
|
|
defp decode_at(bin, offset, labels, visited) do
|
|
case bin do
|
|
<<_::binary-size(offset), 0, _::binary>> ->
|
|
{:ok, {Enum.reverse(labels), offset + 1}}
|
|
|
|
<<_::binary-size(offset), len::8, _::binary>> when (len &&& 0xC0) == 0xC0 ->
|
|
<<_::binary-size(offset), ptr::16, _::binary>> = bin
|
|
<<_::2, pointer::14>> = <<ptr::16>>
|
|
|
|
case decode(bin, pointer, [], visited) do
|
|
{:ok, {suffix, _}} -> {:ok, {Enum.reverse(labels) ++ suffix, offset + 2}}
|
|
{:error, _} = error -> error
|
|
end
|
|
|
|
<<_::binary-size(offset), len::8, _::binary>> ->
|
|
with {:ok, {label, next_offset}} <- Binary.take_binary(bin, offset + 1, len) do
|
|
decode(bin, next_offset, [label | labels], visited)
|
|
end
|
|
|
|
_ ->
|
|
{:error, :invalid_name}
|
|
end
|
|
end
|
|
end
|