%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.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.erlang.org/EPL1_0.txt
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Original Code is Erlang-4.7.3, December, 1998.
%% 
%% The Initial Developer of the Original Code is Ericsson Telecom
%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%% Telecom AB. All Rights Reserved.
%% 
%% Contributor(s): ______________________________________.''
%%
%%%----------------------------------------------------------------------
%%% File    : erl_epmd_server.erl
%%% Author  : Magnus Fr|berg <magnus@erix.ericsson.se>
%%% Purpose : 
%%% Created : 11 Jun 1997 by Magnus Fr|berg <magnus@erix.ericsson.se>
%%%----------------------------------------------------------------------

-module(erl_epmd_server).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/free/1').
-author('magnus@erix.ericsson.se').

-behaviour(gen_server).

%% External exports
-export([start_link/0, stop/0, node_alive/3, node_stop/1, kill/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).

%% internal exports
-export([accept/2]).

-record(state, {
		listen,
		accept,
	        nodes
	       }).

-record(node, {
	       name,
	       port,
	       next_creation, % Next creation number
	       socket,
	       fd,
	       state,         % up | down
	       mod_time       % Modification time
	       }).

-include("erl_epmd.hrl").

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link() ->  gen_server:start_link({local, erl_epmd_server},
			     erl_epmd_server, [], []).

node_alive(Name, Port, Socket) -> gen_server:call(erl_epmd_server,
						  {alive, Port, Name, Socket}).

stop() -> gen_server:call(erl_epmd_server, stop).

node_stop(Name) -> gen_server:call(erl_epmd_server, {stop, Name}).

kill() ->
    case inet_tcp:connect({127,0,0,1}, ?erlang_daemon_port, []) of
	{ok, Socket} ->
	    inet_tcp:send(Socket, put_int16(1) ++ [?EPMD_KILL]),
	    receive
		{tcp, Socket, ["OK"]} ->
		    inet_tcp:close(Socket),
		    ok;
		{tcp_closed, Socket} ->
		    ok
	    after 1000 ->
		    inet_tcp:close(Socket),
		    {error, not_responding}
	    end;
	What ->
	    {error, {listen, What}}
    end.

%%%----------------------------------------------------------------------

%%%----------------------------------------------------------------------

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------
init([]) ->
    process_flag(trap_exit, true),
    case inet_tcp:listen(?erlang_daemon_port, [{reuseaddr, true}]) of
	{ok, Listen} ->
	    Accept = spawn_link(erl_epmd_server, accept,
				[Listen, self()]),
	    NodesT = ets:new(erl_epmd_nodes, [named_table, protected,
					      {keypos, 2}]),
	    {ok, #state{listen = Listen,
			accept = Accept,
			nodes = NodesT}};
	{error, Reason} ->
	    {stop, {error, {listen, Reason}}}
    end.

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------
handle_call({alive, Port, Name, Socket}, _From, State) ->
    delete_too_old(),
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    {reply, busy, State};
	{ok, Node} ->
	    Creation = Node#node.next_creation,
	    insert_node(Node, Port, Name, Socket, Creation),
	    {reply, {ok, Creation}, State};
	_ ->
	    Creation = init_creation(),
	    insert_node(#node{}, Port, Name, Socket, Creation),
	    {reply, {ok, Creation}, State}
    end;
handle_call({stop, Name}, _From, State) ->
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    inet_tcp:close(Node#node.socket),
	    insert(Node#node{state = down,
			     mod_time = now(),
			     fd = -1}),
	    {reply, ok, State};
	{ok, Node} ->
	    {reply, inactive_node, State};
	_ ->
	    {reply, non_existing_node, State}
    end;
handle_call(stop, _From, State) ->
    {stop, shutdown, ok, State}.

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------
handle_cast(Msg, State) ->
    {noreply, State}.

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------
handle_info({'EXIT',Accept,Reason}, State) when State#state.accept == Accept ->
    NewAccept = spawn_link(erl_epmd_server, accept, [State#state.listen,
						     self()]),
    {noreply, State#state{accept = NewAccept}};
handle_info({'EXIT',Listen,Reason}, State) when State#state.listen == Listen ->
    {stop, {error, {listen, Reason}}, State};
handle_info({'EXIT',Socket,Reason}, State) ->
    terminated_connection(Socket),
    {noreply, State};
handle_info(_, State) ->
    {noreply, State}.

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------
terminate(Reason, State) ->
    ok.

%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

accept(Listen, Epmd) ->
    case inet_tcp:accept(Listen) of
	{ok, Socket} ->
	    handle_request(Socket, Epmd),
	    accept(Listen, Epmd);
	Error ->
	    exit(Error)
    end.

handle_request(Socket, Epmd) ->
    receive
	{tcp, Socket, [Hi,Lo|Data]} ->
	    case get_int16(Hi,Lo) of
		Length when Length == length(Data) ->
		    handle_request(Socket, Epmd, Data);
		_ ->
		    error_logger:error_msg("Bad request to epmd - ~w~n",
					   [[Hi,Lo|Data]]),
		    inet_tcp:close(Socket)
	    end;
	{tcp_closed, Socket} ->
	    ok
    end.

handle_request(Socket, Epmd, [?EPMD_PORT_PLEASE|Name0]) ->
    Name = delete_null(Name0),
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    inet_tcp:send(Socket, put_int16(Node#node.port)),
	    inet_tcp:close(Socket),
	    ok;
	_ ->
	    inet_tcp:close(Socket),
	    ok
    end;
handle_request(Socket, Epmd, [?EPMD_ALIVE,Po1,Po2|Name0]) ->
    Port = get_int16(Po1,Po2),
    Name = delete_null(Name0),
    case node_alive(Name, Port, Socket) of
	{ok, Creation} ->
	    handle_alive(Socket, Epmd, Name, Port, Creation),
	    ok;
	busy -> %% Busy
	    inet_tcp:close(Socket),
	    ok
    end;
handle_request(Socket, Epmd, [?EPMD_NAMES]) ->
    inet_tcp:send(Socket, put_int32(?erlang_daemon_port)),
    send_names(active, Socket),
    inet_tcp:close(Socket),
    ok;
handle_request(Socket, Epmd, [?EPMD_DUMP]) ->
    inet_tcp:send(Socket, put_int32(?erlang_daemon_port)),
    send_names(dump, Socket),
    inet_tcp:close(Socket),
    ok;
handle_request(Socket, Epmd, [?EPMD_KILL]) ->
    inet_tcp:send(Socket, "OK"),
    inet_tcp:close(Socket),
    stop(),
    ok;
handle_request(Socket, Epmd, [?EPMD_STOP|Name0]) ->
    %% This message is not used in the emulator or
    %% epmd command interface !!
    Name = delete_null(Name0),
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    node_stop(Name),
	    inet_tcp:send(Socket, "STOPPED"),
	    inet_tcp:close(Socket),
	    ok;
	_ ->
	    inet_tcp:send(Socket, "NOEXIST"),
	    inet_tcp:close(Socket),
	    ok
    end;
handle_request(Socket, Epmd, What) ->
    error_logger:error_msg("Epmd server got garbage - ~w~n", [What]),
    ok.

send_names(Type, Socket) ->
    send_names(first(), Type, Socket).

send_names('$end_of_table', _, _) ->
    ok;
send_names(Name, active, Socket) ->
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    S = io_lib:format("name ~s at port ~p~n", [Name, Node#node.port]),
	    inet_tcp:send(Socket, S),
	    send_names(next(Name), active, Socket);
	_ ->
	    send_names(next(Name), active, Socket)
    end;
send_names(Name, dump, Socket) ->
    case lookup(Name) of
	{ok, Node} when Node#node.state == up ->
	    S = io_lib:format("active name     ~s at port ~p, "
			      "fd = ~p ~n",
			      [Name, Node#node.port, Node#node.fd]),
	    inet_tcp:send(Socket, S),
	    send_names(next(Name), dump, Socket);
	{ok, Node} ->
	    S = io_lib:format("old/unused name ~s at port ~p, "
			      "fd = ~p~n",
			      [Name, Node#node.port, Node#node.fd]),
	    inet_tcp:send(Socket, S),
	    send_names(next(Name), dump, Socket);
	_ ->
	    send_names(next(Name), dump, Socket)
    end.

handle_alive(Socket, Epmd, Name, Port, Creation) ->
    inet_tcp:send(Socket, [?EPMD_ALIVE_OK|put_int16(Creation)]),
    inet_tcp:controlling_process(Socket, Epmd),
    flush(Socket, Epmd).

flush(Socket, Epmd) ->
    receive
	{tcp_closed, Socket} ->
	    Epmd ! {tcp_closed, Socket}
    after 1 ->
	    ok
    end.

delete_null(S)  -> del_null(lists:reverse(S)).
del_null([0|S]) -> lists:reverse(S);
del_null(S)     -> lists:reverse(S).

%%----------------------------------------------------------------------
%% 
%%----------------------------------------------------------------------

terminated_connection(Socket) ->
    terminated_connection(first(), Socket).

terminated_connection('$end_of_table', _) ->
    ok;
terminated_connection(Name, Socket) ->
    case lookup(Name) of
	{ok, Node} when Node#node.socket == Socket ->
	    insert(Node#node{state = down,
			     mod_time = now(),
			     fd = -1}),
	    ok;
	_ ->
	    terminated_connection(next(Name), Socket)
    end.

%%----------------------------------------------------------------------
%% Misc. 
%%----------------------------------------------------------------------

get_int16(Hi,Lo) ->
    ((Hi bsl 8) band 16#ff00) bor (Lo band 16#ff).

put_int16(I) ->
    [((I band 16#ff00) bsr 8),I band 16#ff].

put_int32(I) ->
    [(I band 16#07000000) bsr 24,   %% max is 117,440,512 (24 bits)
     (I band 16#00ff0000) bsr 16,
     (I band 16#0000ff00) bsr 8,
     (I band 16#ff)].

init_creation() ->
    {A1, A2, A3} = seed(),
    R = A1/30269 + A2/30307 + A3/30323,
    trunc((R - trunc(R)) * 3) + 1.

seed() ->
    {A1,A2,A3} = now(),
    {abs(A1) rem 30269, abs(A2) rem 30307, abs(A3) rem 30323}.

add_creation(3) -> 1;
add_creation(N) -> N + 1.

delete_too_old() ->
    Now = calendar:now_to_datetime(now()),
    delete_too_old(first(), Now).

%%
%% Delete all old connections that have been unused for more
%% than an hour.
%% We keep them that long in order to maintain the creation number.
%%
delete_too_old('$end_of_table', _) ->
    ok;
delete_too_old(Name, Now) ->
    Next = next(Name),
    case lookup(Name) of
	{ok, Node} when Node#node.state == down ->
	    ModTime = calendar:now_to_datetime(Node#node.mod_time),
	    case  calendar:time_difference(ModTime, Now) of
		{Day, {_, _, _}} when Day > 0 ->
		    delete(Name),
		    delete_too_old(Next, Now);
		{_, {Hour, _, _}} when Hour > 0 ->
		    delete(Name),
		    delete_too_old(Next, Now);
		_ ->
		    delete_too_old(Next, Now)
	    end;
	_ ->
	    delete_too_old(Next, Now)
    end.

%%----------------------------------------------------------------------
%% erl_epmd_nodes interface.
%%----------------------------------------------------------------------

lookup(Name) ->
    case ets:lookup(erl_epmd_nodes, Name) of
	[Node] ->
	    {ok, Node};
	_ ->
	    false
    end.

insert_node(Node, Port, Name, Socket, Creation) ->
    Node1 = Node#node{name = Name,
		      port = Port,
		      next_creation = add_creation(Creation),
		      socket = Socket,
		      fd = 1,
		      state = up,
		      mod_time = now()},
    insert(Node1).


insert(Node) -> ets:insert(erl_epmd_nodes, Node).

delete(Name) -> ets:delete(erl_epmd_nodes, Name).

first() -> ets:first(erl_epmd_nodes).

next(Name) -> ets:next(erl_epmd_nodes, Name).
