Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,48 @@
`OTC_NO_ASN1=true`, `OTC_NO_DIA=true` before compiling otc (not that
the value doesn't matter).

* Options

Options are given as a map.

Parameters:

stop_after - the value is given as an atom; the protocol after which
to stop decoding. If not given, will try to decode as
much as possible.

Protocol - where Protocol is the atom name of the codec, e.g. m2pa, mtp3,
sccp, nas_eps, sgsap, gtpv1_c, gtpv2_c.
The Value should be the option map for the specified Protocol.

#+BEGIN_SRC erlang
otc:codec({m3ua, Bin}, #{stop_after => sccp, sccp => #{address_type => ansi}}).
#+END_SRC

** sccp options

Parameters:

address_type - used to determine if the SCCP address is ANSI or ITU
formatted. Default is ITU.

#+BEGIN_SRC erlang
#{address_type => ansi}
#+END_SRC

** nas_eps options

Parameters:

direction - used to determine if the message is UE originating or
UE terminating, which is needed for for instance detach-requests.
Without this option OTC will try to determine it itself.

#+BEGIN_SRC erlang
#{direction := 'id-downlinkNASTransport' | 'id-uplinkNASTransport'}
#+END_SRC


* Linguistics

decode - function that converts from binary format to an Erlang map
Expand Down
83 changes: 58 additions & 25 deletions src/otc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,24 @@
| nas_eps | nas_eps_emm | nas_eps_esm
| gtpv1c | gtpv2c | sgsap.

next({sctp_ppi, V}) -> otc_sctp_ppi:next(V);
next({sctp, V}) -> otc_sctp:next(V);
next({m2pa, V}) -> otc_m2pa:next(V);
next({m3ua, V}) -> otc_m3ua:next(V);
next({mtp3, V}) -> otc_mtp3:next(V);
next({sccp, V}) -> otc_sccp:next(V);
next({sccp_mgmt, V}) -> otc_sccp_mgmt:next(V);
next({tcap, V}) -> otc_tcap:next(V);
next({map, V}) -> otc_map:next(V);
next({nas_eps, V}) -> otc_nas_eps:next(V);
next({nas_eps_emm, V}) -> otc_nas_eps_emm:next(V);
next({nas_eps_esm, V}) -> otc_nas_eps_esm:next(V);
next({nas_5gs, V}) -> otc_nas_5gs:next(V);
next({nas_5gs_5gmm, V}) -> otc_nas_5gs_5gmm:next(V);
next({nas_5gs_5gsm, V}) -> otc_nas_5gs_5gsm:next(V);
next({gtpv1c, V}) -> otc_gtpv1c:next(V);
next({gtpv2c, V}) -> otc_gtpv2c:next(V);
next({sgsap, V}) -> otc_sgsap:next(V).
callback_module(sctp_ppi) -> otc_sctp_ppi;
callback_module(sctp) -> otc_sctp;
callback_module(m2pa) -> otc_m2pa;
callback_module(m3ua) -> otc_m3ua;
callback_module(mtp3) -> otc_mtp3;
callback_module(sccp) -> otc_sccp;
callback_module(sccp_mgmt) -> otc_sccp_mgmt;
callback_module(tcap) -> otc_tcap;
callback_module(map) -> otc_map;
callback_module(nas_eps) -> otc_nas_eps;
callback_module(nas_eps_emm) -> otc_nas_eps_emm;
callback_module(nas_eps_esm) -> otc_nas_eps_esm;
callback_module(nas_5gs) -> otc_nas_5gs;
callback_module(nas_5gs_5gmm) -> otc_nas_5gs_5gmm;
callback_module(nas_5gs_5gsm) -> otc_nas_5gs_5gsm;
callback_module(gtpv1c) -> otc_gtpv1c;
callback_module(gtpv2c) -> otc_gtpv2c;
callback_module(sgsap) -> otc_sgsap.

sctp_ppi(PPI) -> sctp_ppi(PPI, #{}).
sctp(D) -> sctp(D, #{}).
Expand Down Expand Up @@ -203,27 +203,60 @@ decode(Proto, Data, Opts) when is_atom(Proto) ->
decode_next({Proto, Data}, Headers, Opts) ->
try ?MODULE:Proto(Data, options(Proto, Opts)) of
{Header, Payload} when is_map(Header) ->
NewHeader = Header#{protocol => Proto},
case next({Proto, Header}, Opts) of
'$stop' ->
{ok, {lists:reverse([Header#{protocol => Proto}|Headers]), Payload}};
{ok, {lists:reverse([NewHeader|Headers]), Payload}};
{ok, Next} ->
decode_next({Next, Payload}, [Header#{protocol => Proto}|Headers], Opts)
next_opts({Next, NewHeader}, Opts),
decode_next({Next, Payload}, [NewHeader|Headers], Opts)
end;
Header when is_map(Header) ->
{ok, lists:reverse([Header#{protocol => Proto}|Headers])}
NewHeader = Header#{protocol => Proto},
{ok, lists:reverse([NewHeader|Headers])}
catch E:R:S ->
?LOG_ERROR(#{E => R, stack => S}),
{error, lists:reverse(Headers), Data}
end.

next({Proto, _Header}, #{stop_after := Proto}) ->
'$stop';
next({Proto, Header}, _) ->
next({Proto, Header}).
next({Proto, V}, _) ->
Module = callback_module(Proto),
Module:next(V).

next_opts({Proto, PrevHeader}, Opts) ->
UserOpts = options(Proto, Opts),
propagated_options({Proto, PrevHeader}, UserOpts).

propagated_options({Proto, PrevHeader}, UserOpts) ->
Module = callback_module(Proto),
case erlang:function_exported(Module, propagated_options, 2) of
true ->
Module:propagated_options(PrevHeader, UserOpts);
false ->
UserOpts
end.

-spec options(protocol(), options()) -> options().
options(Proto, Opts) when is_atom(Proto) ->
maps:get(Proto, Opts, #{}).
%%% Inherits options for sub protocols/modules if they are not
%%% explicitly specified by the user.
%%% e.g. lets the sccp_mgmt layer inherit the options from sccp layer.
options(Proto, Opts) when is_atom(Proto), is_map(Opts), is_map_key(Proto, Opts) ->
maps:get(Proto, Opts);
options(sccp_mgmt, Opts) ->
options(sccp, Opts);
options(nas_eps_emm, Opts) ->
options(nas_eps, Opts);
options(nas_eps_esm, Opts) ->
options(nas_eps, Opts);
options(nas_5gs_5gmm, Opts) ->
options(nas_5gs, Opts);
options(nas_5gs_5gsm, Opts) ->
options(nas_5gs, Opts);
options(Proto, Opts) when is_atom(Proto), is_map(Opts) ->
%% No options found
#{}.

-spec otc:encapsulate(payload()) -> data().
-spec otc:encapsulate(payload(), options()) -> data().
Expand Down
3 changes: 3 additions & 0 deletions src/otc_codec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
-callback codec(binary() | map() | part(), options()) -> map() | binary().

-callback next(map()) -> '$stop' | {ok, atom()}.

-callback propagated_options(map(), options()) -> options().
-optional_callbacks([propagated_options/2]).