<commit_before>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
%% This file contains code that encode/decode modules generated by
%% diameter_codegen.erl calls to implement the functionality. This
%% code does most of the work, the generated code being kept simple.
%%

-module(diameter_gen).

-export([encode_avps/3,
         decode_avps/3,
         grouped_avp/4,
         empty_group/2,
         empty/2]).

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

-define(THROW(T), throw({?MODULE, T})).

-type parent_name()   :: atom().  %% parent = Message or AVP
-type parent_record() :: tuple(). %%
-type avp_name()   :: atom().
-type avp_record() :: tuple().
-type avp_values() :: [{avp_name(), term()}].

-type non_grouped_avp() :: #diameter_avp{}.
-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]).
-type avp() :: non_grouped_avp() | grouped_avp().

%% ---------------------------------------------------------------------------
%% # encode_avps/3
%% ---------------------------------------------------------------------------

-spec encode_avps(parent_name(),
                  parent_record() | avp_values() | map(),
                  map())
   -> iolist()
    | no_return().

encode_avps(Name, Vals, #{module := Mod} = Opts) ->
    try
        encode(Name, Vals, Opts, Mod)
    catch
        throw: {?MODULE, Reason} ->
            diameter_lib:log({encode, error},
                             ?MODULE,
                             ?LINE,
                             {Reason, Name, Vals, Mod}),
            erlang:error(list_to_tuple(Reason ++ [Name]));
        error: Reason ->
            Stack = erlang:get_stacktrace(),
            diameter_lib:log({encode, failure},
                             ?MODULE,
                             ?LINE,
                             {Reason, Name, Vals, Mod, Stack}),
            erlang:error({encode_failure, Reason, Name, Stack})
    end.

%% encode/4

encode(Name, Vals, #{ordered_encode := false} = Opts, Mod)
  when is_list(Vals) ->
    lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals);

encode(Name, Vals, Opts, Mod)
  when is_list(Vals) ->
    encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod);

encode(Name, Map, Opts, Mod)
  when is_map(Map) ->
    [enc(Name, F, A, V, Opts, Mod) || {F,A} <- Mod:avp_arity(Name),
                                      V <- [maps:get(F, Map, def(A))]];

encode(Name, Rec, Opts, Mod) ->
    [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)].

%% encode/5

encode(Name, AvpName, Values, Opts, Mod) ->
    enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod).

%% enc/6

enc(_, AvpName, 1, undefined, _, _) ->
    ?THROW([mandatory_avp_missing, AvpName]);

enc(Name, AvpName, 1, Value, Opts, Mod) ->
    enc(Name, AvpName, [Value], Opts, Mod);

enc(_, _, {0,_}, [], _, _) ->
    [];

enc(_, AvpName, _, T, _, _)
  when not is_list(T) ->
    ?THROW([repeated_avp_as_non_list, AvpName, T]);

enc(_, AvpName, {Min, _}, L, _, _)
  when length(L) < Min ->
    ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]);

enc(_, AvpName, {_, Max}, L, _, _)
  when Max < length(L) ->
    ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]);

enc(Name, AvpName, _, Values, Opts, Mod) ->
    enc(Name, AvpName, Values, Opts, Mod).

%% enc/5

enc(Name, 'AVP', Values, Opts, Mod) ->
    [enc_AVP(Name, A, Opts, Mod) || A <- Values];

enc(_, AvpName, Values, Opts, Mod) ->
    enc(AvpName, Values, Opts, Mod).

%% enc/4

enc(AvpName, Values, Opts, Mod) ->
    H = Mod:avp_header(AvpName),
    [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts))
     || V <- Values].

%% enc_AVP/4

%% No value: assume AVP data is already encoded. The normal case will
%% be when this is passed back from #diameter_packet.errors as a
%% consequence of a failed decode. Any AVP can be encoded this way
%% however, which side-steps any arity checks for known AVP's and
%% could potentially encode something unfortunate.
enc_AVP(_, #diameter_avp{value = undefined} = A, Opts, _) ->
    diameter_codec:pack_avp(A, Opts);

%% Missing name for value encode.
enc_AVP(_, #diameter_avp{name = N, value = V}, _, _)
  when N == undefined;
       N == 'AVP' ->
    ?THROW([value_with_nameless_avp, N, V]);

%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we
%% don't know this AVP at all then the encode will fail.
enc_AVP(Name, #diameter_avp{name = AvpName, value = Data}, Opts, Mod) ->
    0 == Mod:avp_arity(Name, AvpName)
        orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]),
    enc(AvpName, [Data], Opts, Mod);

%% The backdoor ...
enc_AVP(_, {AvpName, Value}, Opts, Mod) ->
    enc(AvpName, [Value], Opts, Mod);

%% ... and the side door.
enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) ->
    diameter_codec:pack_avp(#diameter_avp{data = T}, Opts).

%% ---------------------------------------------------------------------------
%% # decode_avps/3
%% ---------------------------------------------------------------------------<commit_msg>Don't take length of AVP lists unnecessarily at encode<commit_after>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
%% This file contains code that encode/decode modules generated by
%% diameter_codegen.erl calls to implement the functionality. This
%% code does most of the work, the generated code being kept simple.
%%

-module(diameter_gen).

-export([encode_avps/3,
         decode_avps/3,
         grouped_avp/4,
         empty_group/2,
         empty/2]).

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

-define(THROW(T), throw({?MODULE, T})).

-type parent_name()   :: atom().  %% parent = Message or AVP
-type parent_record() :: tuple(). %%
-type avp_name()   :: atom().
-type avp_record() :: tuple().
-type avp_values() :: [{avp_name(), term()}].

-type non_grouped_avp() :: #diameter_avp{}.
-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]).
-type avp() :: non_grouped_avp() | grouped_avp().

%% ---------------------------------------------------------------------------
%% # encode_avps/3
%% ---------------------------------------------------------------------------

-spec encode_avps(parent_name(),
                  parent_record() | avp_values() | map(),
                  map())
   -> iolist()
    | no_return().

encode_avps(Name, Vals, #{module := Mod} = Opts) ->
    try
        encode(Name, Vals, Opts, Mod)
    catch
        throw: {?MODULE, Reason} ->
            diameter_lib:log({encode, error},
                             ?MODULE,
                             ?LINE,
                             {Reason, Name, Vals, Mod}),
            erlang:error(list_to_tuple(Reason ++ [Name]));
        error: Reason ->
            Stack = erlang:get_stacktrace(),
            diameter_lib:log({encode, failure},
                             ?MODULE,
                             ?LINE,
                             {Reason, Name, Vals, Mod, Stack}),
            erlang:error({encode_failure, Reason, Name, Stack})
    end.

%% encode/4

encode(Name, Vals, #{ordered_encode := false} = Opts, Mod)
  when is_list(Vals) ->
    lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals);

encode(Name, Vals, Opts, Mod)
  when is_list(Vals) ->
    encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod);

encode(Name, Map, Opts, Mod)
  when is_map(Map) ->
    [enc(Name, F, A, V, Opts, Mod) || {F,A} <- Mod:avp_arity(Name),
                                      V <- [maps:get(F, Map, def(A))]];

encode(Name, Rec, Opts, Mod) ->
    [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)].

%% encode/5

encode(Name, AvpName, Values, Opts, Mod) ->
    enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod).

%% enc/6

enc(_, AvpName, 1, undefined, _, _) ->
    ?THROW([mandatory_avp_missing, AvpName]);

enc(Name, AvpName, 1, Value, Opts, Mod) ->
    H = avp_header(AvpName, Mod),
    enc1(Name, AvpName, H, Value, Opts, Mod);

enc(_, _, {0,_}, [], _, _) ->
    [];

enc(_, AvpName, _, T, _, _)
  when not is_list(T) ->
    ?THROW([repeated_avp_as_non_list, AvpName, T]);

enc(Name, AvpName, {Min, Max}, Values, Opts, Mod) ->
    H = avp_header(AvpName, Mod),
    enc(Name, AvpName, H, Min, 0, Max, Values, Opts, Mod).

%% enc/9

enc(_, AvpName, _, Min, N, _, [], _, _)
  when N < Min ->
    ?THROW([repeated_avp_insufficient_arity, AvpName, Min, N]);

enc(_, _, _, _, _, _, [], _, _) ->
    [];

enc(_, AvpName, _, _, N, Max, _, _, _)
  when Max =< N ->
    ?THROW([repeated_avp_excessive_arity, AvpName, Max]);

enc(Name, AvpName, H, Min, N, Max, [V|Vs], Opts, Mod) ->
    [enc1(Name, AvpName, H, V, Opts, Mod)
     | enc(Name, AvpName, H, Min, N+1, Max, Vs, Opts, Mod)].

%% avp_header/2

avp_header('AVP', _) ->
    false;

avp_header(AvpName, Mod) ->
    {_,_,_} = Mod:avp_header(AvpName).

%% enc1/6

enc1(Name, 'AVP', false, Value, Opts, Mod) ->
    enc_AVP(Name, Value, Opts, Mod);

enc1(_, AvpName, Hdr, Value, Opts, Mod) ->
    enc1(AvpName, Hdr, Value, Opts, Mod).

%% enc1/5

enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod) ->
    diameter_codec:pack_data(Hdr, Mod:avp(encode, Value, AvpName, Opts)).

%% enc_AVP/4

%% No value: assume AVP data is already encoded. The normal case will
%% be when this is passed back from #diameter_packet.errors as a
%% consequence of a failed decode. Any AVP can be encoded this way
%% however, which side-steps any arity checks for known AVP's and
%% could potentially encode something unfortunate.
enc_AVP(_, #diameter_avp{value = undefined} = A, Opts, _) ->
    diameter_codec:pack_avp(A, Opts);

%% Missing name for value encode.
enc_AVP(_, #diameter_avp{name = N, value = V}, _, _)
  when N == undefined;
       N == 'AVP' ->
    ?THROW([value_with_nameless_avp, N, V]);

%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we
%% don't know this AVP at all then the encode will fail.
enc_AVP(Name, #diameter_avp{name = AvpName, value = Data}, Opts, Mod) ->
    0 == Mod:avp_arity(Name, AvpName)
        orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]),
    enc(AvpName, Data, Opts, Mod);

%% The backdoor ...
enc_AVP(_, {AvpName, Value}, Opts, Mod) ->
    enc(AvpName, Value, Opts, Mod);

%% ... and the side door.
enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) ->
    diameter_codec:pack_avp(#diameter_avp{data = T}, Opts).

%% enc/4

enc(AvpName, Value, Opts, Mod) ->
    enc1(AvpName, Mod:avp_header(AvpName), Value, Opts, Mod).

%% ---------------------------------------------------------------------------
%% # decode_avps/3
%% ---------------------------------------------------------------------------
{'fraction_non_alphanumeric': 0.20736040609137055, 'fraction_numerical': 0.006091370558375634, 'mean_word_length': 2.4901092412164156, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '352414', 'n_tokens_mistral': 4139, 'n_tokens_neox': 4047, 'n_words': 1096}