From b4276b98b005557b24ea6d390d4363dd16194098 Mon Sep 17 00:00:00 2001 From: Sebastian Weddmark Olsson Date: Tue, 2 Sep 2025 20:44:50 +0200 Subject: [PATCH 1/2] Rewrite how options are passed around --- README.org | 42 ++++++++++++++++++++++++ src/otc.erl | 81 +++++++++++++++++++++++++++++++++-------------- src/otc_codec.erl | 3 ++ 3 files changed, 102 insertions(+), 24 deletions(-) diff --git a/README.org b/README.org index 83e7d3a..25ab231 100644 --- a/README.org +++ b/README.org @@ -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 diff --git a/src/otc.erl b/src/otc.erl index 3511314..a543fd5 100644 --- a/src/otc.erl +++ b/src/otc.erl @@ -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, #{}). @@ -203,14 +203,17 @@ 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} @@ -219,11 +222,41 @@ decode_next({Proto, Data}, Headers, Opts) -> next({Proto, _Header}, #{stop_after := Proto}) -> '$stop'; next({Proto, Header}, _) -> - next({Proto, Header}). + 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(). diff --git a/src/otc_codec.erl b/src/otc_codec.erl index 0a1c107..9924a8d 100644 --- a/src/otc_codec.erl +++ b/src/otc_codec.erl @@ -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]). From 4d789d22f01947f981c29798c66053507951b2f7 Mon Sep 17 00:00:00 2001 From: Sebastian Weddmark Olsson Date: Tue, 2 Sep 2025 20:47:05 +0200 Subject: [PATCH 2/2] rename parameter --- src/otc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otc.erl b/src/otc.erl index a543fd5..6e0e36e 100644 --- a/src/otc.erl +++ b/src/otc.erl @@ -221,7 +221,7 @@ decode_next({Proto, Data}, Headers, Opts) -> next({Proto, _Header}, #{stop_after := Proto}) -> '$stop'; -next({Proto, Header}, _) -> +next({Proto, V}, _) -> Module = callback_module(Proto), Module:next(V).