%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2002-2020. 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%
%%

-module(erts_test_utils).
-compile(r22).

%%
%% THIS MODULE IS ALSO USED BY *OTHER* APPLICATIONS TEST CODE
%%

-export([mk_ext_pid/3,
         mk_ext_port/2,
         mk_ext_ref/2,
         available_internal_state/1,
         check_node_dist/0, check_node_dist/1, check_node_dist/3]).



-define(VERSION_MAGIC,       131).

-define(ATOM_EXT,            100).
-define(REFERENCE_EXT,       101).
-define(PORT_EXT,            102).
-define(PID_EXT,             103).
-define(NEW_REFERENCE_EXT,   114).
-define(NEW_PID_EXT,         $X).
-define(NEW_PORT_EXT,        $Y).
-define(NEWER_REFERENCE_EXT, $Z).
-define(V4_PORT_EXT,         $x).

-define(OLD_MAX_PIDS_PORTS, ((1 bsl 28) - 1)).

uint64_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 64 ->
    [(Uint bsr 56) band 16#ff,
     (Uint bsr 48) band 16#ff,
     (Uint bsr 40) band 16#ff,
     (Uint bsr 32) band 16#ff,
     (Uint bsr 24) band 16#ff,
     (Uint bsr 16) band 16#ff,
     (Uint bsr 8) band 16#ff,
     Uint band 16#ff];
uint64_be(Uint) ->
    exit({badarg, uint64_be, [Uint]}).

uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 ->
    [(Uint bsr 24) band 16#ff,
     (Uint bsr 16) band 16#ff,
     (Uint bsr 8) band 16#ff,
     Uint band 16#ff];
uint32_be(Uint) ->
    exit({badarg, uint32_be, [Uint]}).

uint16_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 16 ->
    [(Uint bsr 8) band 16#ff,
     Uint band 16#ff];
uint16_be(Uint) ->
    exit({badarg, uint16_be, [Uint]}).

uint8(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 8 ->
    Uint band 16#ff;
uint8(Uint) ->
    exit({badarg, uint8, [Uint]}).

pid_tag(bad_creation) -> ?PID_EXT;
pid_tag(Creation) when Creation =< 3 -> ?PID_EXT;
pid_tag(_Creation) -> ?NEW_PID_EXT.

enc_creation(bad_creation) -> uint8(4);
enc_creation(Creation) when Creation =< 3 -> uint8(Creation);
enc_creation(Creation) -> uint32_be(Creation).

mk_ext_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) ->
    mk_ext_pid({atom_to_list(NodeName), Creation}, Number, Serial);
mk_ext_pid({NodeName, Creation}, Number, Serial) ->
    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
					      pid_tag(Creation),
					      ?ATOM_EXT,
					      uint16_be(length(NodeName)),
					      NodeName,
					      uint32_be(Number),
					      uint32_be(Serial),
					      enc_creation(Creation)])) of
	Pid when is_pid(Pid) ->
	    Pid;
	{'EXIT', {badarg, _}} ->
	    exit({badarg, mk_pid, [{NodeName, Creation}, Number, Serial]});
	Other ->
	    exit({unexpected_binary_to_term_result, Other})
    end.

port_tag(_Num, bad_creation) ->
    ?PORT_EXT;
port_tag(Num, Creation) when 0 =< Num, Num =< ?OLD_MAX_PIDS_PORTS, Creation =< 3 ->
    ?PORT_EXT;
port_tag(Num, _Creation) when 0 =< Num, Num =< ?OLD_MAX_PIDS_PORTS ->
    ?NEW_PORT_EXT;
port_tag(_Num, _Creation) ->
    ?V4_PORT_EXT.

mk_ext_port({NodeName, Creation}, Number) when is_atom(NodeName) ->
    mk_ext_port({atom_to_list(NodeName), Creation}, Number);
mk_ext_port({NodeName, Creation}, Number) ->
    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
					      port_tag(Number, Creation),
					      ?ATOM_EXT,
					      uint16_be(length(NodeName)),
					      NodeName,
					      case Number > ?OLD_MAX_PIDS_PORTS of
						  true -> uint64_be(Number);
						  false -> uint32_be(Number)
					      end,
					      enc_creation(Creation)])) of
	Port when is_port(Port) ->
	    Port;
	{'EXIT', {badarg, _}} ->
	    exit({badarg, mk_port, [{NodeName, Creation}, Number]});
	Other ->
	    exit({unexpected_binary_to_term_result, Other})
    end.

ref_tag(bad_creation) -> ?NEW_REFERENCE_EXT;
ref_tag(Creation) when Creation =< 3 -> ?NEW_REFERENCE_EXT;
ref_tag(_Creation) -> ?NEWER_REFERENCE_EXT.

mk_ext_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
					   is_list(Numbers) ->
    mk_ext_ref({atom_to_list(NodeName), Creation}, Numbers);
mk_ext_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
                                                Creation =< 3,
                                                is_integer(Number) ->
    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
                                              ?REFERENCE_EXT,
                                              ?ATOM_EXT,
                                              uint16_be(length(NodeName)),
                                              NodeName,
                                              uint32_be(Number),
                                              uint8(Creation)])) of
        Ref when is_reference(Ref) ->
            Ref;
        {'EXIT', {badarg, _}} ->
            exit({badarg, mk_ref, [{NodeName, Creation}, [Number]]});
        Other ->
            exit({unexpected_binary_to_term_result, Other})
    end;
mk_ext_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
                                               is_list(Numbers) ->
    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
					      ref_tag(Creation),
					      uint16_be(length(Numbers)),
					      ?ATOM_EXT,
					      uint16_be(length(NodeName)),
					      NodeName,
					      enc_creation(Creation),
					      lists:map(fun (N) ->
								uint32_be(N)
							end,
							Numbers)])) of
	Ref when is_reference(Ref) ->
	    Ref;
	{'EXIT', {badarg, _}} ->
	    exit({badarg, mk_ref, [{NodeName, Creation}, Numbers]});
	Other ->
	    exit({unexpected_binary_to_term_result, Other})
    end.


available_internal_state(Bool) when Bool == true; Bool == false ->
    case {Bool,
          (catch erts_debug:get_internal_state(available_internal_state))} of
        {true, true} ->
            true;
        {false, true} ->
            erts_debug:set_internal_state(available_internal_state, false),
            true;
        {true, _} ->
            erts_debug:set_internal_state(available_internal_state, true),
            false;
        {false, _} ->
            false
    end.


%%
%% Check reference counters for node- and dist entries.
%%
check_node_dist() ->
    check_node_dist(fun(ErrMsg) ->
                            io:format("check_node_dist ERROR:\n~p\n", [ErrMsg]),
                            error
                    end).

check_node_dist(Fail) ->
    AIS = available_internal_state(true),
    [erlang:garbage_collect(P) || P <- erlang:processes()],
    {{node_references, NodeRefs},
     {dist_references, DistRefs}} =
        erts_debug:get_internal_state(node_and_dist_references),
    R = check_node_dist(Fail, NodeRefs, DistRefs),
    available_internal_state(AIS),
    R.

check_node_dist(Fail, NodeRefs, DistRefs) ->
    AIS = available_internal_state(true),
    R = check_nd_refc({node(),erlang:system_info(creation)},
                  NodeRefs, DistRefs, Fail),
    available_internal_state(AIS),
    R.


check_nd_refc({ThisNodeName, ThisCreation}, NodeRefs, DistRefs, Fail) ->
    case catch begin
                   check_refc(ThisNodeName,ThisCreation,"node table",NodeRefs),
                   check_refc(ThisNodeName,ThisCreation,"dist table",DistRefs),
                   ok
               end of
        ok ->
            ok;
        {'EXIT', Reason} ->
            {Y,Mo,D} = date(),
            {H,Mi,S} = time(),
            ErrMsg = io_lib:format("~n"
                                   "*** Reference count check of node ~w "
                                   "failed (~p) at ~w~w~w ~w:~w:~w~n"
                                   "*** Node table references:~n ~p~n"
                                   "*** Dist table references:~n ~p~n",
                                   [node(), Reason, Y, Mo, D, H, Mi, S,
                                    NodeRefs, DistRefs]),
            Fail(lists:flatten(ErrMsg))
    end.


check_refc(ThisNodeName,ThisCreation,Table,EntryList) when is_list(EntryList) ->
    lists:foreach(
      fun ({Entry, Refc, ReferrerList}) ->
              {DelayedDeleteTimer,
               FoundRefs} =
              lists:foldl(
                fun ({Referrer, ReferencesList}, {DDT, A1}) ->
                        {case Referrer of
                             {system,delayed_delete_timer} ->
                                 true;
                             {system,thread_progress_delete_timer} ->
                                 true;
                             _ ->
                                 DDT
                         end,
                         A1 + lists:foldl(fun ({_T,Rs},A2) ->
                                                  A2+Rs
                                          end,
                                          0,
                                          ReferencesList)}
                end,
                {false, 0},
                ReferrerList),

              %% Reference count equals found references?
              case {Refc, FoundRefs, DelayedDeleteTimer} of
                  {X, X, _} ->
                      ok;
                  {0, 1, true} ->
                      ok;
                  _ ->
                      exit({invalid_reference_count, Table, Entry})
              end,

              %% All entries in table referred to?
              case {Entry, Refc} of
                  {ThisNodeName, 0} -> ok;
                  {{ThisNodeName, ThisCreation}, 0} -> ok;
                  {_, 0} when DelayedDeleteTimer == false ->
                      exit({not_referred_entry_in_table, Table, Entry});
                  {_, _} -> ok 
              end

      end,
      EntryList),
    ok.
