Current section
Files
Jump to
Current section
Files
src/otc.erl
-module(otc).
%% Inspired by [pkt](https://github.com/msantos/pkt) and [ossie](https://github.com/massemanet/ossie)
%% Supported protocol
-export([sctp_ppi/1,
%% sctp/1,
m2pa/1,
mtp3/1,
m3ua/1,
sccp/1,
tcap/1,
map/1,
nas_eps/1,
nas_eps_emm/1,
nas_eps_esm/1,
nas_5gs/1,
nas_5gs_5gmm/1,
nas_5gs_5gsm/1
]).
%% General functions
-export([%% decapsulate/1,
decapsulate/2,
decapsulate/3,
%% decode/1,
decode/2,
decode/3,
encode/1]).
-include_lib("kernel/include/logger.hrl").
%% Supported protocols ---------------------------------------------------------
-type protocol() :: sctp_ppi | m3ua | m2pa | mtp3 | sccp | tcap | map | nas_eps | nas_eps_emm | nas_eps_esm.
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({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).
sctp_ppi(PPI) -> otc_sctp_ppi:codec(PPI).
%% sctp(D) -> otc_sctp:codec(D).
m2pa(D) -> otc_m2pa:codec(D).
mtp3(D) -> otc_mtp3:codec(D).
m3ua(D) -> otc_m3ua:codec(D).
sccp(D) -> otc_sccp:codec(D).
tcap(D) -> otc_tcap:codec(D).
map(D) -> otc_map:codec(D).
nas_eps(D) -> otc_nas_eps:codec(D).
nas_eps_emm(D) -> otc_nas_eps_emm:codec(D).
nas_eps_esm(D) -> otc_nas_eps_esm:codec(D).
nas_5gs(D) -> otc_nas_5gs:codec(D).
nas_5gs_5gmm(D) -> otc_nas_5gs_5gmm:codec(D).
nas_5gs_5gsm(D) -> otc_nas_5gs_5gsm:codec(D).
%% General functions -----------------------------------------------------------
-type options() :: #{stop_after => protocol()}.
-type data() :: binary() | non_neg_integer().
-type header() :: map().
-type headers() :: [header()].
-type packet() :: headers() | Decoded :: {headers(), data()} | Decapsulated :: [header() | binary()].
-spec otc:decapsulate(protocol(), data()) -> packet().
-spec otc:decapsulate(protocol(), data(), options()) -> packet().
%% decapsulate/1,2 works on valid packets. If the packet is malformed
%% or unsupported, decapsulate/1 will crash.
decapsulate(Proto, Data) ->
decapsulate(Proto, Data, #{}).
decapsulate(PPI, Data, Opts) when is_integer(PPI) ->
decapsulate_next(sctp_ppi(PPI), Data, [], Opts);
decapsulate(Proto, Data, Opts) when is_atom(Proto) ->
decapsulate_next(Proto, Data, [], Opts).
%% decapsulate(Data) when is_binary(Data) ->
%% decapsulate_next({sctp, Data}, []).
decapsulate_next('$stop', Data, Headers, _Opts) ->
lists:reverse([Data|Headers]);
decapsulate_next(Proto, Data, Headers, #{stop_after := Proto}) ->
lists:reverse([Data|Headers]);
decapsulate_next(Proto, Data, Headers, Opts) ->
{Header, Payload} = ?MODULE:Proto(Data),
decapsulate_next(next({Proto, Header}, Opts), Payload, [Header|Headers], Opts).
%% -spec otc:decode(data()) ->
%% {ok, packet()} |
%% {error, SoFar :: headers(), {FailedProto :: protocol(), data()}}.
-spec otc:decode(protocol(), data()) ->
{ok, packet()} |
{error, SoFar :: headers(), {FailedProto :: protocol(), data()}}.
-spec otc:decode(protocol(), data(), options()) ->
{ok, packet()} |
{error, SoFar :: headers(), {FailedProto :: protocol(), data()}}.
%% Similar to decapsulate/1 but, on error, returns any part of the
%% packet that has been successfully converted to Erlang term format.
%% decode(Data) when is_binary(Data) ->
%% decode(sctp, Data).
decode(P, Data) ->
decode(P, Data, #{}).
decode(PPI, Data, Opts) when is_integer(PPI) ->
decode(sctp_ppi(PPI), Data, Opts);
decode(Proto, Data, Opts) when is_atom(Proto) ->
decode_next({Proto, Data}, [], Opts).
decode_next({Proto, Data}, Headers, Opts) ->
try ?MODULE:Proto(Data) of
{Header, Payload} when is_map(Header) ->
case next({Proto, Header}, Opts) of
'$stop' ->
{ok, {lists:reverse([Header#{protocol => Proto}|Headers]), Payload}};
{ok, Next} ->
decode_next({Next, Payload}, [Header#{protocol => Proto}|Headers], Opts)
end;
Header when is_map(Header) ->
{ok, lists:reverse([Header#{protocol => Proto}|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}).
encode(#{protocol := Proto} = Pdu) ->
?MODULE:Proto(Pdu);
encode(Pdus) when is_list(Pdus) ->
encode({Pdus, <<>>});
encode({Pdus, Payload}) when is_list(Pdus) or is_tuple(Pdus), is_binary(Payload) ->
lists:foldr(fun (P, Acc) ->
B = encode(P),
<<B/binary, Acc/binary>>
end, Payload, Pdus);
encode({#{protocol := Proto} = Pdu, Payload}) ->
?MODULE:Proto({Pdu, Payload});
encode({Proto, Data}) when is_atom(Proto) ->
?MODULE:Proto(Data);
encode(Data) when is_binary(Data) ->
Data.