]> git.frustrated-labs.net Git - dns-phrasex.git/commitdiff
feat: start adding encoder
authorAlexander Goussas <[email protected]>
Sun, 1 Feb 2026 00:46:46 +0000 (19:46 -0500)
committerAlexander Goussas <[email protected]>
Sun, 1 Feb 2026 00:46:46 +0000 (19:46 -0500)
lib/dns_phrases/message.ex
lib/dns_phrases/message/encoder.ex [new file with mode: 0644]
lib/dns_phrases/message/parser.ex
lib/dns_phrases/reply.ex [new file with mode: 0644]
lib/dns_phrases/server.ex

index e0ac29c3ece5a8b41670f6f97addd6b856c359b3..6969793637b8ddcae1357bb57d3a12adf1952d44 100644 (file)
@@ -1,8 +1,13 @@
 defmodule DnsPhrases.Message do
-  defstruct [:op, tid: 0]
+  defstruct [:name, :tid, :type, :class]
 
   @typedoc """
   A message received from a client.
   """
-  @type t :: %DnsPhrases.Message{op: integer, tid: integer}
+  @type t :: %DnsPhrases.Message{
+          name: list(),
+          tid: integer(),
+          type: integer(),
+          class: integer()
+        }
 end
diff --git a/lib/dns_phrases/message/encoder.ex b/lib/dns_phrases/message/encoder.ex
new file mode 100644 (file)
index 0000000..2bd8653
--- /dev/null
@@ -0,0 +1,35 @@
+defmodule DnsPhrases.Message.Encoder do
+  alias DnsPhrases.Reply
+  alias DnsPhrases.Message
+
+  @spec encode(Reply.t()) :: binary()
+  def encode(%Reply{reply_to: reply_to, phrase: phrase}) do
+    <<
+      reply_to.tid::integer-size(16),
+      1::1,
+      0::15,
+      1::16,
+      1::16,
+      0::32
+    >> <>
+      encode_question(reply_to) <>
+      encode_answer(reply_to, phrase)
+  end
+
+  @spec encode_name(Message.t()) :: binary()
+  defp encode_name(msg) do
+    msg.name
+    |> Enum.map(fn part -> <<String.length(part)::integer-size(8)>> <> part <> <<0::8>> end)
+    |> Enum.join(".")
+  end
+
+  @spec encode_question(Message.t()) :: binary()
+  defp encode_question(msg) do
+    encode_name(msg) <> <<msg.type::integer-size(16), msg.class::integer-size(16)>>
+  end
+
+  @spec encode_answer(Message.t(), binary) :: binary()
+  defp encode_answer(msg, phrase) do
+    encode_name(msg) <> <<16::16, 256::16, 0::32, String.length(phrase)::16>> <> phrase
+  end
+end
index 835ab737f9e0868789fe85d39ba67df9e41e7546..50591284e100340812f70b5b5fc29bbfddf98434 100644 (file)
@@ -1,10 +1,9 @@
 defmodule DnsPhrases.Message.Parser do
   alias DnsPhrases.Message
 
-  @spec parse(bitstring) :: DnsPhrases.Message.t()
-
+  @spec parse(bitstring()) :: DnsPhrases.Message.t()
   def parse(<<
-        tid::16,
+        tid::integer-size(16),
         0::1,
         _flags::15,
         1::integer-size(16),
@@ -13,19 +12,26 @@ defmodule DnsPhrases.Message.Parser do
         _additional_rrs::16,
         data::bitstring
       >>) do
-    op = parse_question(data)
-    %Message{tid: tid, op: op}
+    {name, type, class} = parse_question(data)
+
+    %Message{
+      name: name,
+      type: type,
+      class: class,
+      tid: tid
+    }
   end
 
-  @spec parse_question(bitstring) :: list
-  def parse_question(data), do: parse_question(data, []) |> Enum.reverse()
+  @spec parse_question(bitstring) :: {list(), integer(), integer()}
+  def parse_question(data), do: parse_question(data, [])
 
-  @spec parse_question(bitstring, list) :: list
+  @spec parse_question(bitstring, list) :: {list(), integer(), integer()}
   defp parse_question(
-         <<n::integer-size(8), data::bitstring-size(n * 8), 0::8, _rest::bitstring>>,
+         <<n::integer-size(8), data::bitstring-size(n * 8), 0::8, type::integer-size(16),
+           class::integer-size(16), _rest::bitstring>>,
          parts
        ) do
-    [data | parts]
+    {Enum.reverse([data | parts]), type, class}
   end
 
   defp parse_question(
diff --git a/lib/dns_phrases/reply.ex b/lib/dns_phrases/reply.ex
new file mode 100644 (file)
index 0000000..9a27ab3
--- /dev/null
@@ -0,0 +1,8 @@
+defmodule DnsPhrases.Reply do
+  defstruct [:reply_to, :phrase]
+
+  @typedoc """
+  The reply sent back to the client.
+  """
+  @type t :: %DnsPhrases.Reply{reply_to: DnsPhrases.Message.t(), phrase: String.t()}
+end
index fcb5d4a1b2facd68063c32fe455b58ebeef620b3..3f310df0d50149a38ca6c8809f109c56133d11bc 100644 (file)
@@ -2,6 +2,8 @@ defmodule DnsPhrases.Server do
   use GenServer
 
   alias DnsPhrases.Message.Parser
+  alias DnsPhrases.Message.Encoder
+  alias DnsPhrases.Reply
 
   defmodule State do
     defstruct([:socket])
@@ -20,14 +22,17 @@ defmodule DnsPhrases.Server do
 
   @impl GenServer
   def handle_info(
-        {:udp, socket, _ip, _port, data},
+        {:udp, socket, ip, port, data},
         %State{socket: socket} = state
       ) do
-    # TODO 1: Parse DNS message
-    Parser.parse(data)
+    msg = Parser.parse(data)
+    ans = Encoder.encode(%Reply{reply_to: msg, phrase: "Hello, world!"})
+
+    case :gen_udp.send(socket, ip, port, ans) do
+      {:error, reason} -> IO.puts(reason)
+      :ok -> {}
+    end
 
-    # TODO 2: Assemble DNS response as a TXT record containing the frame
-    # TODO 3: Reply to our client
     {:noreply, state}
   end
 end