Skip to content

Commit

Permalink
Encode / Decode Implementation (#2)
Browse files Browse the repository at this point in the history
* Issue #1: Encode / Decode Implementation

* Issue #1: Implemented lists:foldl

* Issue #1: Implemented suggestions

* Issue#1: Missing rebar.config

* rebar3 temp files / folders ignored

* parser missing support WIP #3

* ignore .rebar3 lib deps temp folder

* PDU templates

* templates API renamed to info

* renamed template API to info

* added update_checksum function

* using stx etx macros (#5)

* using stx and etx macros

* repleaced include_lib with incldue, formatting changes
  • Loading branch information
kirankurup authored and c-bik committed Mar 23, 2018
1 parent 0c97624 commit 896136e
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 74 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
_build/
erl_crash.dump
ebin
rel/example_project
.concrete/DEV_MODE
.rebar
rebar.lock
_build/
.rebar3/
rebar.lock
rebar3.crashdump
tmp
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

{profiles, [
{test, [
{deps, [{jsx, {git, "https://github.com/K2InformaticsGmbH/jsx.git", {tag, "v2.8.2"}}}]},
{plugins, [
{coveralls, {git, "https://github.com/markusn/coveralls-erl", {branch, "master"}}},
{geas_rebar3, {git, "https://github.com/crownedgrouse/geas_rebar3.git", {branch, "master"}}}
Expand Down
253 changes: 197 additions & 56 deletions src/ucp.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-module(ucp).
-include("ucp_defines.hrl").

-export([parse/1, pack/1, parse_stream/1, cmdstr/2]).
-export([parse/1, pack/1, parse_stream/1, cmdstr/2,
decode/1, encode/1, info/0, update_checksum/1]).

parse_stream(Bytes) ->
case re:run(Bytes, "(\x02[^\x02\x03]+)",
Expand All @@ -18,7 +20,7 @@ parse_stream(Bytes) ->

parse_frames([], Ucps) -> Ucps;
parse_frames([[Frame|_]|Frames], Ucps) ->
FramePatched = Frame++[3],
FramePatched = Frame++[?ETX],
parse_frames(Frames, [
case (catch parse(FramePatched)) of
{'EXIT', _} ->
Expand All @@ -32,10 +34,10 @@ parse(Bytes) when is_binary(Bytes) ->
parse(binary_to_list(Bytes));
parse(UcpString) when is_list(UcpString) ->
case {hd(UcpString), lists:last(UcpString)} of
{2,3} -> ucp_syntax:parse(UcpString);
{_,3} -> parse([2|UcpString]);
{2,_} -> parse(UcpString++[3]);
{_,_} -> parse([2|UcpString]++[3])
{?STX, ?ETX} -> ucp_syntax:parse(UcpString);
{_ , ?ETX} -> parse([2|UcpString]);
{?STX, _} -> parse(UcpString++[3]);
{_ , _} -> parse([2|UcpString]++[3])
end.

pack([{A,_}|_] = Ucp) when is_atom(A) ->
Expand Down Expand Up @@ -67,13 +69,187 @@ cmd(C) when is_binary(C) -> <<"unknown_",C/binary>>;
cmd(C) when is_integer(C) -> <<"unknown_",(integer_to_binary(C))/binary>>;
cmd(C) -> <<"unknown_",(list_to_binary(io_lib:format("~p",[C])))/binary>>.

-spec(decode(Str :: list() | binary()) -> {ok, map()} | {error, binary()}).
decode(Str) when is_list(Str); is_binary(Str) ->
try
{ok, pl_to_map(parse(Str))}
catch
_:_ ->
io:format("Error: ~p~n", [erlang:get_stacktrace()]),
{error, <<"Invalid UCP Message">>}
end;
decode(_) ->
{error, <<"Input to decode should be either binary or string">>}.

-spec(pl_to_map(list()) -> map()).
pl_to_map(PL) ->
lists:foldl(fun pl_to_map/2, #{}, PL).

-spec(pl_to_map({atom(), term()}, map()) -> map()).
pl_to_map({K, V}, AccMap) when is_list(V) ->
AccMap#{atom_to_binary(K, utf8) => list_to_binary(V)};
pl_to_map({K, V}, AccMap) ->
AccMap#{atom_to_binary(K, utf8) => V}.

-spec(encode(PDU :: map()) -> {ok, binary()} | {error, binary()}).
encode(PDU) when is_map(PDU) ->
[?STX|T_DATA] = pack(map_to_pl(PDU)),
[?ETX|UCP_DATA] = lists:reverse(T_DATA),
{ok, list_to_binary(lists:reverse(UCP_DATA))};
encode(_) ->
{error, <<"Input to encode should be map">>}.

-spec(map_to_pl(map()) -> list()).
map_to_pl(PDU) ->
maps:fold(fun map_to_pl/3, [], PDU).

-spec(map_to_pl(term(), term(), list()) -> list()).
map_to_pl(K, V, Acc) when is_binary(V) ->
[{binary_to_atom(K, utf8), binary_to_list(V)} | Acc];
map_to_pl(K, V, Acc) ->
[{binary_to_atom(K, utf8), V} | Acc].

% 01 31 51 52 53 55 56 57 58 60
-define(BASE(_OR, _OT),
#{<<"type">> => <<_OR>>, <<"ot">> => _OT, <<"trn">> => 0}
).

-define(ACK(_OR,_OT), ?BASE(_OR,_OT)#{<<"ack">> => <<"A">>}).
-define(MSG(_OR,_OT), ?BASE(_OR,_OT)#{<<"adc">> => <<>>,<<"msg">> => <<>>,<<"mt">> => 3}).

-define(O01, <<"O/01 Call Input">>).
-define(O31, <<"O/31 SMT Alert">>).
-define(O51, <<"O/51 Submit Short Message">>).
-define(O52, <<"O/52 Deliver Short Message">>).
-define(O53, <<"O/53 Deliver Notification">>).
-define(O55, <<"O/55 Inquiry Message">>).
-define(O56, <<"O/56 Delete Message">>).
-define(O57, <<"O/57 Response Inquiry Message">>).
-define(O58, <<"O/58 Response Delete Message">>).
-define(O60, <<"O/60 Session Management">>).
-define(R01, <<"R/01 Call Input">>).
-define(R31, <<"R/31 SMT Alert">>).
-define(R51, <<"R/51 Submit Short Message">>).
-define(R52, <<"R/52 Deliver Short Message">>).
-define(R53, <<"R/53 Deliver Notification">>).
-define(R55, <<"R/55 Inquiry Message">>).
-define(R56, <<"R/56 Delete Message">>).
-define(R57, <<"R/57 Response Inquiry Message">>).
-define(R58, <<"R/58 Response Delete Message">>).
-define(R60, <<"R/60 Session Management">>).

info() ->
#{templates =>
#{?O01 => ?MSG($O,01),
?O31 => ?BASE($O,31)#{<<"adc">> => <<>>,<<"pid">> => 100},
?O51 => ?MSG($O,51)#{<<"oadc">> => <<>>},
?O52 => ?MSG($O,52)#{<<"dcs">> => 0, <<"oadc">> => <<>>},
?O53 => ?MSG($O,53)#{<<"dst">> => 1,<<"oadc">> => <<>>,<<"rsn">> => 108},
?O55 => ?BASE($O,55)#{<<"adc">> => <<>>,<<"oadc">> => <<>>},
?O56 => ?MSG($O,56)#{<<"oadc">> => <<>>},
?O57 => ?MSG($O,57),
?O58 => ?MSG($O,58),
?O60 => ?BASE($O,60)#{<<"oadc">> => <<>>,<<"pwd">> => <<>>,<<"styp">> => 1,<<"vers">> => 100},

?R01 => ?ACK($R,01),
?R31 => ?ACK($R,31)#{<<"sm">> => <<"0000">>},
?R51 => ?ACK($R,51),
?R52 => ?ACK($R,52),
?R53 => ?ACK($R,53),
?R55 => ?ACK($R,55),
?R56 => ?ACK($R,56),
?R57 => ?ACK($R,57),
?R58 => ?ACK($R,58),
?R60 => ?ACK($R,60)}
}.

% 13 bytes header
% TRN(2) '/' Len(5) '/' OP(1) '/' OT(2)
% 3 bytes = TRN '/'
% 5 bytes = Len
% 5 bytes = '/' OP(1) '/' OT(2)
update_checksum(<<?STX, _:3/binary, Len:5/binary
, _:5/binary, Rest/binary>> = Pdu) ->
LenI = binary_to_integer(Len),
% if there is a checksum, Rest will have atleast 3 bytes
% including / after header and Len field in header will
% also indicate the same, hence also replace checksum
if (byte_size(Rest) >= 3) andalso LenI > 2 ->
BodyLen = LenI-2,
<<?STX, Body:BodyLen/binary
, _:2/binary, BodyRest/binary>> = Pdu,
[A, B|_] = lists:reverse(
integer_to_list(
lists:sum(binary_to_list(Body)), 16)),
Checksum = list_to_binary([B,A]),
<<?STX, Body:BodyLen/binary
, Checksum:2/binary, BodyRest/binary>>;
true -> Pdu
end.

-ifdef(TEST).
%%
%% EUnit tests
%%

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

ucp(OR, OT, Data) ->
% stx TRN(2) / LENGTH(5) / OR(1) / OT(2) / Data / CHK(2) etx
Len = 2 +1 +5 +1 +1 +1 +2 +1 +length(Data) +1 +2,
LenStr = string:pad(integer_to_list(Len), 5, leading, $0),
Pdu = ["00", "/", LenStr, "/", OR, "/", OT, "/", Data, "/"],
PduStr = lists:flatten(Pdu),
% The <checksum> is derived by the addition of all bytes of the header,
% data field separators and data fields (i.e., all characters after the
% stx character, up to and including the last “/” before the checksum
% field). The eight Least Significant Bits (LSB) of the result is then
% represented as two printable characters. The character containing four
% Most Significant Bits (MSB) (of those eight LSB) shall be transmitted
% first. For example, if the checksum is 3A(hex), the representation shall
% be the characters “3” (33(hex)) and “A” (41(hex)).
Checksum = integer_to_list(lists:sum(PduStr) band 16#FF, 16),
PduStr ++ Checksum.

ucp(OT, Data) -> ucp("O", OT, Data).
ucp_ack(OT, Data) -> ucp("R", OT, [$A, $/ | Data]).
ucp_nack(OT, Data) -> ucp("R", OT, [$N, $/ | Data]).

% 01 31 51 52 53 55 56 57 58 60
-define(TESTS,
[
{"O-1_Num", ucp("01","0123456789///2/")},
{"O-1_AlNum", ucp("01","0123456789///3/414243")},
{"O-31", ucp("31","0123456789/0100")},
{"O-51_Num", ucp("51","0123456789/0123456789/////////////////2//////////////")},
%{"O-51a_Num", ucp("51","0123456789/0D41E110306C1E01/////////////////2//////////5039////")},
{"O-51_AlNum", ucp("51","0123456789/0123456789/////////////////3//414243////////////")},
%{"O-51a_AlNum", ucp("51","0123456789/0D41E110306C1E01/////////////////3//414243////////5039////")},
{"O-51_Trans", ucp("51","0123456789/0123456789/////////////////4//58595A////////////")},
%{"O-51a_Trans", ucp("51","0123456789/0D41E110306C1E01/////////////////4//58595A////////5039////")},
%{"O-52_Num", ucp("52","0123456789/0123456789/////////////311299235959////2//////////////")},
%{"O-52_AlNum", ucp("52","0123456789/0123456789/////////////311299235959////3//414243////////////")},
%{"O-52_Trans", ucp("52","0123456789/0123456789/////////////311299235959////4//58595A////////////")},
%{"O-53", ucp("53","0123456789/0123456789/////////////311299235959/0/0/311299235959/3//414243////////////")},
{"O-55", ucp("55","0123456789/0123456789///////////////////////////////")},
%{"O-56", ucp("56","0123456789/0123456789/////////////////3//414243////////////")},
{"O-57", ucp("57","0123456789//////////////////3//414243////////////")},
{"O-58", ucp("58","0123456789//////////////////3//414243////////////")},
{"O-60", ucp("60","0123456789///1/313233414243//0100/////")},

%{"R-1_NA", "00/00022/R/01/N/02//00"},
{"R-1_A", ucp_ack("01","")},
{"R-31_A", ucp_ack("31","0000")},
%{"R-51_A", ucp_ack("51","/")},
{"R-52_A", ucp_ack("52","/")},
{"R-53_A", ucp_ack("53","/")},
{"R-55_A", ucp_ack("55","/")},
{"R-56_A", ucp_ack("56","/")},
{"R-57_A", ucp_ack("57","/")},
{"R-58_A", ucp_ack("58","/")},
{"R-60_A", ucp_ack("60","")}
]).

parse_test_() ->
{inparallel,
[{T, fun() ->
Expand All @@ -85,56 +261,21 @@ parse_test_() ->
end,
?assertMatch([_|_], P)
end}
|| {T, D} <-
[
{"O-1_Num", "00/00032/O/01/0123456789///2//EB"},
{"O-1_AlNum", "00/00038/O/01/0123456789///3/414243/24"},
{"O-2_Num", "00/00045/O/02/2/0123456789/0123456789///2//8D"},
{"O-2_AlNum", "00/00051/O/02/2/0123456789/0123456789///3/414243/BD"},
{"O-3_Num", "00/00044/O/03/0123456789///0///////////2//25"},
{"O-3_AlNum", "00/00050/O/03/0123456789///0///////////3/414243/55"},
{"O-30", "00/00042/O/30/0123456789/////////414243/D9"},
{"O-31", "00/00032/O/31/0123456789/0100/F0"},
{"O-51_Num", "00/00070/O/51/0123456789/0123456789/////////////////2///////////////23"},
{"O-51a_Num", "00/00080/O/51/0123456789/0D41E110306C1E01/////////////////2//////////5039/////4B"},
{"O-51_AlNum", "00/00076/O/51/0123456789/0123456789/////////////////3//414243/////////////5C"},
{"O-51a_AlNum", "00/00086/O/51/0123456789/0D41E110306C1E01/////////////////3//414243////////5039/////84"},
{"O-51a_AlNum", "00/00086/O/51/0123456789/0D41E110306C1E01/////////////////3//414243////////5039/////84"},
{"O-51_Trans", "00/00076/O/51/0123456789/0123456789/////////////////4//58595A/////////////7C"},
{"O-51a_Trans", "00/00086/O/51/0123456789/0D41E110306C1E01/////////////////4//58595A////////5039/////A4"},
{"O-52_Num", "00/00082/O/52/0123456789/0123456789/////////////311299235959////2///////////////A1"},
{"O-52_AlNum", "00/00088/O/52/0123456789/0123456789/////////////311299235959////3//414243/////////////DA"},
{"O-52_Trans", "00/00088/O/52/0123456789/0123456789/////////////311299235959////4//58595A/////////////FA"},
{"O-53", "00/00102/O/53/0123456789/0123456789/////////////311299235959/0/0/311299235959/3//414243/////////////A8"},
{"O-53", "00/00102/O/53/0123456789/0123456789/////////////311299235959/0/0/311299235959/3//414243/////////////A8"},
{"O-54_Num", "00/00082/O/54/0123456789/0123456789/////////////311299235959////2///////////////A3"},
{"O-54_AlNum", "00/00088/O/54/0123456789/0123456789/////////////311299235959////3//414243/////////////DC"},
{"O-54_Trans", "00/00088/O/54/0123456789/0123456789/////////////311299235959////4//58595A/////////////FC"},
{"O-55", "00/00069/O/55/0123456789/0123456789////////////////////////////////FD"},
{"O-56", "00/00076/O/56/0123456789/0123456789/////////////////3//414243/////////////61"},
{"O-57", "00/00066/O/57/0123456789//////////////////3//414243/////////////54"},
{"O-58", "00/00066/O/58/0123456789//////////////////3//414243/////////////55"},
{"O-60", "00/00055/O/60/0123456789///1/313233414243//0100//////5F"},
{"O-61", "00/00043/O/61/0123456789///1///0100//////FC"},

{"R-1_A", "00/00019/R/01/A//68"},
{"R-2_A", "00/00019/R/02/A//69"},
{"R-3_A", "00/00019/R/03/A//6A"},
{"R-30_A", "00/00020/R/30/A///91"},
{"R-31_A", "00/00020/R/31/A/0/93"},
{"R-51_A", "00/00020/R/51/A///94"},
{"R-52_A", "00/00020/R/52/A///95"},
{"R-53_A", "00/00020/R/53/A///96"},
{"R-54_A", "00/00020/R/54/A///97"},
{"R-55_A", "00/00020/R/55/A///98"},
{"R-56_A", "00/00020/R/56/A///99"},
{"R-57_A", "00/00020/R/57/A///9A"},
{"R-58_A", "00/00020/R/58/A///9B"},
{"R-60_A", "00/00019/R/60/A//6D"},
{"R-61_A", "00/00019/R/61/A//6E"},

{"R-1_NA", "00/00022/R/01/N/02//00"}
]
|| {T, D} <- ?TESTS
]}.

encode_decode_test_() ->
{inparallel,
[{T,
fun() ->
{ok, D} = decode(P),
?assertEqual(true, is_map(D)),
{ok, E} = encode(jsx:decode(jsx:encode(D), [return_maps])),
?debugFmt("~p", [decode(E)]),
?assertEqual(true, is_binary(E)),
?assertEqual({ok, D}, decode(E))
end
} || {T,P} <- ?TESTS]
}.

-endif. %TEST
6 changes: 6 additions & 0 deletions src/ucp_defines.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
-define(UCP_XSER_SERVICE_GSM_UDH_APPLICATION_PORTS, 4).


%%% stx and etx

-define(STX, 2).
-define(ETX, 3).


%%% TODO - there are lots more constants in the specification


Expand Down
Loading

0 comments on commit 896136e

Please sign in to comment.