Current section
Files
Jump to
Current section
Files
lib/mndp/packet.ex
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(<<type::8, ttl::8, seq_no::16, data::binary>>) 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)
<<mndp.type::8, mndp.ttl::8, mndp.seq_no::16>> <> tlv
end
defp decode_tlv(<<>>, acc), do: {:ok, acc}
defp decode_tlv(<<type::16, length::16, data::binary>>, acc) do
case data do
<<data::bytes-size(length), rest::binary>> ->
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
<<seconds::unsigned-integer-little-size(length)-unit(8)>> = 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(<<q1::8, q2::8, q3::8, q4::8>>), do: {q1, q2, q3, q4}
defp parse_ipv6(<<h1::16, h2::16, h3::16, h4::16, h5::16, h6::16, h7::16, h8::16>>),
do: {h1, h2, h3, h4, h5, h6, h7, h8}
defp parse_mac(<<m1::8, m2::8, m3::8, m4::8, m5::8, m6::8>>), do: [m1, m2, m3, m4, m5, m6]
defp to_tlv({:mac, [m1, m2, m3, m4, m5, m6]}),
do: {1, 6, <<m1::8, m2::8, m3::8, m4::8, m5::8, m6::8>>}
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, <<data::little-32>>}
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, <<h1::16, h2::16, h3::16, h4::16, h5::16, h6::16, h7::16, h8::16>>}
defp to_tlv({:interface, data}), do: {16, byte_size(data), data}
defp to_tlv({:ip_v4, {q1, q2, q3, q4}}), do: {17, 4, <<q1::8, q2::8, q3::8, q4::8>>}
defp to_tlv(_), do: nil
defp encode_tlv({type, length, data}), do: <<type::16, length::16, data::binary>>
defp encode_tlv(_), do: <<>>
end