Current section
Files
Jump to
Current section
Files
lib/aadya/parser.ex
defmodule Aadya.MessageParser do
@moduledoc ~S"""
CoapMessage Parser
Aadya.MessageParser.decode
Aadya.MessageParser.encode
Supported CoAP v1 messages
"""
require Record
use Aadya.Header
import Aadya.Header
import Typex
@version 1
@option_if_match 1
@option_uri_host 3
@option_etag 4
@option_if_none_match 5
@option_observe 6
@option_uri_port 7
@option_location_path 8
@option_uri_path 11
@option_content_format 12
@option_max_age 14
@option_uri_query 15
@option_accept 17
@option_location_query 20
@option_block2 23
@option_block1 27
@option_proxy_uri 35
@option_proxy_scheme 39
@option_size1 60
def decode(<<@version::2, type::2, 0::4, 0::3, 0::5, msg_id::16>>) do
{true, coap_message(coap_message, type: decode_type(type), id: msg_id)}
end
def decode(
<<@version::2, type::2, token_length::4, class::3, code::5, msg_id::16,
token::bytes-size(token_length), tail::bytes>>
) do
{options, payload} = decode_option_list(tail)
{true,
coap_message(
coap_message,
type: decode_type(type),
method: methods({class, code}),
id: msg_id,
token: token,
options: options,
payload: payload
)}
end
def decode(<<tail::bytes>>) do
{false, tail}
end
# % empty message
def encode(coap_message(type: type, method: nil, id: msg_id) = msg) do
<<@version::2, encode_type(type)::2, 0::4, 0::3, 0::5, msg_id::16>>
end
def encode(
coap_message(
type: type,
method: method,
id: msg_id,
token: token,
options: options,
payload: payload
) = msg
) do
token_length = byte_size(token)
{class, code} = methods(method)
tail = encode_option_list(options, payload)
<<@version::2, encode_type(type)::2, token_length::4, class::3, code::5,
msg_id::16, token::bytes-size(token_length), tail::binary>>
end
def message_id(<<_::16, msg_id::16, _tail::bytes>>), do: msg_id
def message_id(coap_message(id: msg_id) = msg), do: msg_id
def decode_option_list(tail) do
decode_option_list(tail, 0, [])
end
def decode_option_list(<<>>, _lastNum, option_list) do
{option_list, <<>>}
end
def decode_option_list(<<0xFF, payload::bytes>>, _lastNum, option_list) do
{option_list, payload}
end
def decode_option_list(
<<delta::4, len::4, tail::bytes>>,
last_num,
option_list
) do
{tail1, opt_num} =
cond do
delta < 13 ->
{tail, last_num + delta}
delta == 13 ->
<<ext_opt_num, new_tail1::bytes>> = tail
{new_tail1, last_num + ext_opt_num + 13}
delta == 14 ->
<<ext_opt_num::16, new_tail1::bytes>> = tail
{new_tail1, last_num + ext_opt_num + 269}
end
{tail2, opt_len} =
cond do
len < 13 ->
{tail1, len}
len == 13 ->
<<ext_opt_len, new_tail2::bytes>> = tail1
{new_tail2, ext_opt_len + 13}
len == 14 ->
<<ext_opt_len::16, new_tail2::bytes>> = tail1
{new_tail2, ext_opt_len + 269}
end
case tail2 do
<<opt_val::bytes-size(opt_len), nextOpt::bytes>> ->
decode_option_list(
nextOpt,
opt_num,
append_option(decode_option(opt_num, opt_val), option_list)
)
<<>> ->
decode_option_list(
<<>>,
opt_num,
append_option(decode_option(opt_num, <<>>), option_list)
)
end
end
def append_option({same_opt_id, opt_val2}, [
{same_opt_id, optVal1} | option_list
]) do
case is_repeatable_option(same_opt_id) do
true ->
[{same_opt_id, optVal1 ++ [opt_val2]} | option_list]
false ->
throw({:error, Atom.to_charlist(same_opt_id) ++ " is not repeatable"})
end
end
def append_option({opt_id2, opt_val2}, option_list) do
case is_repeatable_option(opt_id2) do
true -> [{opt_id2, [opt_val2]} | option_list]
false -> [{opt_id2, opt_val2} | option_list]
end
end
def encode_option_list(options, <<>>), do: options |> encode_option_list1
def encode_option_list(options, payload) do
<<encode_option_list1(options)::bytes, 0xFF, payload::bytes>>
end
def encode_option_list1(options) do
options1 = encode_options(options, [])
encode_option_list(:lists.keysort(1, options1), 0, <<>>)
end
def encode_options([{_optId, nil} | option_list], acc) do
encode_options(option_list, acc)
end
def encode_options([{optId, opt_val} | option_list], acc) do
case is_repeatable_option(optId) do
true ->
encode_options(
option_list,
split_and_encode_option({optId, opt_val}, acc)
)
false ->
encode_options(option_list, [encode_option({optId, opt_val}) | acc])
end
end
def encode_options([], acc), do: acc
def split_and_encode_option({optId, [nil | optVals]}, acc) do
split_and_encode_option({optId, optVals}, acc)
end
def split_and_encode_option({optId, [optVal1 | optVals]}, acc) do
[
encode_option({optId, optVal1})
| split_and_encode_option({optId, optVals}, acc)
]
end
def split_and_encode_option({_optId, []}, acc), do: acc
def encode_option_list([{opt_num, opt_val} | option_list], last_num, acc) do
{delta, ext_num} =
cond do
opt_num - last_num >= 269 ->
{14, <<opt_num - last_num - 269::16>>}
opt_num - last_num >= 13 ->
{13, <<opt_num - last_num - 13>>}
true ->
{opt_num - last_num, <<>>}
end
{len, ext_len} =
cond do
byte_size(opt_val) >= 269 ->
{14, <<byte_size(opt_val) - 269::16>>}
byte_size(opt_val) >= 13 ->
{13, <<byte_size(opt_val) - 13>>}
true ->
{byte_size(opt_val), <<>>}
end
acc2 =
<<acc::bytes, delta::4, len::4, ext_num::bytes, ext_len::bytes,
opt_val::bytes>>
encode_option_list(option_list, opt_num, acc2)
end
def encode_option_list([], _lastNum, acc), do: acc
def is_repeatable_option(:if_match), do: true
def is_repeatable_option(:etag), do: true
def is_repeatable_option(:location_path), do: true
def is_repeatable_option(:uri_path), do: true
def is_repeatable_option(:uri_query), do: true
def is_repeatable_option(:location_query), do: true
def is_repeatable_option(_else), do: false
# % RFC 7252
def decode_option(@option_if_match, opt_val), do: {:if_match, opt_val}
def decode_option(@option_uri_host, opt_val), do: {:uri_host, opt_val}
def decode_option(@option_etag, opt_val), do: {:etag, opt_val}
def decode_option(@option_if_none_match, <<>>), do: {:if_none_match, true}
def decode_option(@option_uri_port, opt_val),
do: {:uri_port, :binary.decode_unsigned(opt_val)}
def decode_option(@option_location_path, opt_val),
do: {:location_path, opt_val}
def decode_option(@option_uri_path, opt_val), do: {:uri_path, opt_val}
def decode_option(@option_content_format, opt_val) do
num = :binary.decode_unsigned(opt_val)
{:content_format, content_formats(num)}
end
def decode_option(@opotion_max_age, opt_val),
do: {:max_age, :binary.decode_unsigned(opt_val)}
def decode_option(@option_uri_query, opt_val), do: {:uri_query, opt_val}
def decode_option(@option_accept, opt_val),
do: {'accept', :binary.decode_unsigned(opt_val)}
def decode_option(@option_location_query, opt_val),
do: {:location_query, opt_val}
def decode_option(@option_proxy_uri, opt_val), do: {:proxy_uri, opt_val}
def decode_option(@option_proxy_scheme, opt_val), do: {:proxy_scheme, opt_val}
def decode_option(@option_size, opt_val),
do: {:size1, :binary.decode_unsigned(opt_val)}
def decode_option(@option_observe, opt_val),
do: {:observe, :binary.decode_unsigned(opt_val)}
def decode_option(@option_block2, opt_val),
do: {:block2, decode_block(opt_val)}
def decode_option(@option_block1, opt_val),
do: {:block1, decode_block(opt_val)}
def decode_option(opt_num, opt_val), do: {opt_num, opt_val}
def decode_block(<<num::4, m::1, sizEx::3>>), do: decode_block1(num, m, sizEx)
def decode_block(<<num::12, m::1, sizEx::3>>),
do: decode_block1(num, m, sizEx)
def decode_block(<<num::28, m::1, sizEx::3>>),
do: decode_block1(num, m, sizEx)
def decode_block1(num, m, sizEx) do
{num,
cond do
m == 0 -> false
true -> true
end, trunc(:math.pow(2, sizEx + 4))}
end
# % RFC 7252
def encode_option({:if_match, opt_val}), do: {@option_if_match, opt_val}
def encode_option({:uri_host, opt_val}), do: {@optin_uri_host, opt_val}
def encode_option({:etag, opt_val}), do: {@option_etag, opt_val}
def encode_option({:if_none_match, true}), do: {@option_if_none_match, <<>>}
def encode_option({:uri_port, opt_val}),
do: {@option_uri_port, :binary.encode_unsigned(opt_val)}
def encode_option({:location_path, opt_val}),
do: {@option_location_path, opt_val}
def encode_option({:uri_path, opt_val}), do: {@option_uri_path, opt_val}
def encode_option({:content_format, opt_val}) when is_integer(opt_val) do
{@option_content_format, :binary.encode_unsigned(opt_val)}
end
def encode_option({:content_format, opt_val}) do
num = content_formats(opt_val)
{@option_content_format, :binary.encode_unsigned(num)}
end
def encode_option({:max_age, opt_val}),
do: {@opton_max_age, :binary.encode_unsigned(opt_val)}
def encode_option({:uri_query, opt_val}), do: {@option_uri_query, opt_val}
def encode_option({'accept', opt_val}),
do: {@option_accept, :binary.encode_unsigned(opt_val)}
def encode_option({:location_query, opt_val}),
do: {@option_locatio_query, opt_val}
def encode_option({:proxy_uri, opt_val}), do: {@option_proxy_uri, opt_val}
def encode_option({:proxy_scheme, opt_val}),
do: {@option_proxy_scheme, opt_val}
def encode_option({:size1, opt_val}),
do: {@opton_size, :binary.encode_unsigned(opt_val)}
def encode_option({:observe, opt_val}),
do: {@option_observe, :binary.encode_unsigned(opt_val)}
def encode_option({:block2, opt_val}),
do: {@option_block2, encode_block(opt_val)}
def encode_option({:block1, opt_val}),
do: {@option_block1, encode_block(opt_val)}
def encode_block({num, more, size}) do
encode_block1(
num,
cond do
more -> 1
1 -> 0
end,
trunc(log2(size)) - 4
)
end
def encode_block1(num, m, sizEx) when num < 16 do
<<num::4, m::1, sizEx::3>>
end
def encode_block1(num, m, sizEx) when num < 4096 do
<<num::12, m::1, sizEx::3>>
end
def encode_block1(num, m, sizEx) do
<<num::28, m::1, sizEx::3>>
end
def log2(x), do: :math.log(x) / :math.log(2)
end