defmodule MNDP.Packet do @moduledoc """ Functions for decoding and encoding the raw packet binary """ @discovery_request <<0, 0, 0, 0>> @doc delegate_to: {MNDP, :decode, 1} @spec decode(binary()) :: {:ok, MNDP.t()} | {:error, atom()} def decode(@discovery_request), do: {:error, :discovery_trigger_packet} def decode(<>) do meta = [type: type, ttl: ttl, seq_no: seq_no] case decode_tlv(data, []) do {:ok, data} -> {:ok, struct(MNDP, Keyword.merge(meta, data))} error -> error end end def decode(_), do: {:error, :unknown_header} @doc delegate_to: {MNDP, :encode, 1} @spec encode(MNDP.t()) :: binary() def encode(%MNDP{} = mndp) do tlv = mndp |> Map.to_list() |> Enum.map(&to_tlv/1) |> Enum.reject(&is_nil/1) |> Enum.sort_by(fn {type, _, _} -> type end, :asc) |> Enum.reduce(<<>>, fn field, acc -> acc <> encode_tlv(field) end) <> <> tlv end defp decode_tlv(<<>>, acc), do: {:ok, acc} defp decode_tlv(<>, acc) do case data do <> -> decode_tlv(rest, [parse_tlv({type, length, data}) | acc]) _ -> {:error, :expected_tlv_format} end end defp decode_tlv(_, _), do: {:error, :expected_tlv_format} defp parse_tlv({1, _length, data}), do: {:mac, parse_mac(data)} defp parse_tlv({5, _length, data}), do: {:identity, data} defp parse_tlv({7, _length, data}), do: {:version, data} defp parse_tlv({8, _length, data}), do: {:platform, data} defp parse_tlv({10, length, data}) do <> = data {:uptime, seconds} end defp parse_tlv({11, _length, data}), do: {:software_id, data} defp parse_tlv({12, _length, data}), do: {:board, data} defp parse_tlv({14, _length, data}), do: {:unpack, parse_unpack(data)} defp parse_tlv({15, _length, data}), do: {:ip_v6, parse_ipv6(data)} defp parse_tlv({16, _length, data}), do: {:interface, data} defp parse_tlv({17, _length, data}), do: {:ip_v4, parse_ipv4(data)} defp parse_unpack(<<1>>), do: :none defp parse_unpack(_), do: nil defp parse_ipv4(<>), do: {q1, q2, q3, q4} defp parse_ipv6(<>), do: {h1, h2, h3, h4, h5, h6, h7, h8} defp parse_mac(<>), do: [m1, m2, m3, m4, m5, m6] defp to_tlv({:mac, [m1, m2, m3, m4, m5, m6]}), do: {1, 6, <>} defp to_tlv({_, nil}), do: nil defp to_tlv({:identity, data}), do: {5, byte_size(data), data} defp to_tlv({:version, data}), do: {7, byte_size(data), data} defp to_tlv({:platform, data}), do: {8, byte_size(data), data} defp to_tlv({:uptime, data}), do: {10, 4, <>} defp to_tlv({:software_id, data}), do: {11, byte_size(data), data} defp to_tlv({:board, data}), do: {12, byte_size(data), data} defp to_tlv({:unpack, :none}), do: {14, 1, <<1::8>>} defp to_tlv({:ip_v6, {h1, h2, h3, h4, h5, h6, h7, h8}}), do: {15, 16, <>} defp to_tlv({:interface, data}), do: {16, byte_size(data), data} defp to_tlv({:ip_v4, {q1, q2, q3, q4}}), do: {17, 4, <>} defp to_tlv(_), do: nil defp encode_tlv({type, length, data}), do: <> defp encode_tlv(_), do: <<>> end