fp-lab4/lib/exdns/dns_packet/name.ex
2026-01-13 18:26:26 +03:00

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