aboutsummaryrefslogtreecommitdiff
path: root/src/coap_parse.erl
blob: bb61a28d0ebeb5e2d49d9cfab53e894ed326d9e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
-module(coap_parse).
-export([parse/1,
         get_option_values/2]).

-include("coaperl.hrl").


-spec parse(<<>>) -> #coap_request{} | #coap_response{} | #coap_empty_message{}.
% @doc: parse a binary coap message
parse(Packet) ->
    {coap, Parsed} = parse_raw(Packet),
    parse_to_rec(Parsed).


% @doc get the named option values from the given response record
get_option_values(#coap_response{options=Options}, OptNum) ->
    [ Val || {coap_option, Num, Val} <- Options, Num =:= OptNum ].


% @doc: convert raw message to request, response or empty message records
parse_to_rec({_Type, Code, MID, _Token, Options, Payload}) when Code > 0, Code =< 31 ->
    #coap_request{method=coap_unparse:method_name(Code),
                  path=parse_path(Options),
                  query=parse_query(Options),
                  mid=MID,
                  ct=parse_content_type(Options),
                  payload=Payload};
parse_to_rec({_Type, 0, MID , _Token, _Options, <<>>}) ->
    #coap_empty_message{mid=MID};
parse_to_rec({_Type, Code, MID, _Token, Options, Payload}) ->
    <<Class:3, Detail:5>> = <<Code>>,
    #coap_response{mid=MID,
                   ct=parse_content_type(Options),
                   options=Options,
                   class=Class,
                   detail=Detail,
                   block2=parse_block2(Options),
                   payload=Payload}.


% @doc: parse path from raw options
parse_path(Options) ->
    erlang:iolist_to_binary(lists:reverse(parse_path(Options, []))).

parse_path([{coap_option, ?COAP_OPTION_URI_PATH, Comp}|T], Acc) ->
    parse_path(T, [["/"|Comp]|Acc]);
parse_path([{coap_option, _, _}|T], Acc) ->
    parse_path(T, Acc);
parse_path([], Acc) ->
    Acc.


% @doc: parse query from raw options
parse_query(Options) ->
    parse_query(Options, []).

parse_query([{coap_option, ?COAP_OPTION_URI_QUERY, Opt}|T], Acc) ->
    parse_query(T, [Opt|Acc]);
parse_query([{coap_option, _, _}|T], Acc) ->
    parse_query(T, Acc);
parse_query([], Acc) ->
    Acc.



% @doc: parse content type from raw options
parse_content_type(Options) ->
    OptVals = [Value || {coap_option, Format, Value} <- Options, Format =:= ?COAP_OPTION_CONTENT_FORMAT],
    case OptVals of
        [<<Val:8>>] -> corerl:content_format_type(Val);
        _ -> undefined
    end.


% @doc: parse block2 option from raw options
parse_block2(Options) ->
    OptVals = [Value || {coap_option, Format, Value} <- Options, Format =:= ?COAP_OPTION_BLOCK2],
    case OptVals of
        [Val] -> 
            NumLen = bit_size(Val)-4,
            <<Num:NumLen, M:1, SZX:3>> = Val,
            {Num, M, 1 bsl (SZX+4)};
        _ -> undefined
    end.


% @doc: parse raw packet
parse_raw(<<Version:2, Type:2, TKL: 4, Code:8, MID:16, Token:TKL/bytes, Tail/bytes>>) when Version =:= 1 ->
    {Options, Payload} = parse_raw_options(Tail),
    {coap, {Type, Code, MID, Token, Options, Payload}};
parse_raw(_AnythingElse) ->
    {bad, "unable to parse packet"}.

% @doc: parse options in raw packet, don't interpret any option types yet
parse_raw_options(OptionBin) ->
    parse_raw_options(OptionBin, 0, []).

parse_raw_options(<<>>, _LastNum, OptionList) ->
    {OptionList, <<>>};
parse_raw_options(<<16#FF, Payload/bytes>>, _LastNum, OptionList) ->
    {OptionList, Payload};
parse_raw_options(<<BaseOptNum:4, BaseOptLen:4, Tail/bytes>>, LastNum, OptionList) ->
    {Tail1, OptNum} = case BaseOptNum of
        X when X < 13 ->
            {Tail, BaseOptNum + LastNum};
        X when X =:= 13 ->
            <<ExtOptNum, NewTail/bytes>> = Tail,
            {NewTail, ExtOptNum + 13 + LastNum};
        X when X =:= 14 ->
            <<ExtOptNum:2/bytes, NewTail/bytes>> = Tail,
            {NewTail, ExtOptNum + 269 + LastNum}
    end,
    {OptLen, Tail3} = case BaseOptLen of
        Y when Y < 13 ->
                {BaseOptLen, Tail1};
        Y when Y =:= 13 ->
                <<ExtOptLen, Tail2/bytes>> = Tail1,
                {ExtOptLen + 13, Tail2};
        Y when Y =:= 14 ->
                <<ExtOptLen:2/bytes, Tail2/bytes>> = Tail1,
                {ExtOptLen + 269, Tail2}
    end,
    <<OptVal:OptLen/bytes, NextOpt/bytes>> = Tail3,
    parse_raw_options(NextOpt, OptNum, [{coap_option, OptNum, OptVal}|OptionList]).

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

parse_to_rec_test() ->
    % Check if parse to the right rec
    ?assertEqual(parse_to_rec({egal, 100, 1, egal, [{coap_option, ?COAP_OPTION_CONTENT_FORMAT, <<50:8>>}], payload}),
                 {coap_response,3,4,1,
                  <<"application/json">>,
                  undefined,
                  [{coap_option,12,<<"2">>}],
                  payload}).
-endif.