%% ``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.erl
%%% Author  : Magnus Fröberg <magnus@erix.ericsson.se>
%%% Purpose : 
%%% Created : 13 Dec 1996 by Magnus Fröberg <magnus@erix.ericsson.se>
%%%----------------------------------------------------------------------

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

-behaviour(gen_server).

%% External exports
-export([start/0, start_link/0, stop/0, port_please/2, names/1,
	 register_node/2, open/0, open/1]).
-export([port_please/3, port_please/4]).
-export([register_node/3,register_node/5]).

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

-import(lists, [reverse/1]).

-record(state, {socket, port_no = -1, name = ""}).

-define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]).

-define(u16(X1,X2),
	(((X1) bsl 8) bor X2)).
 
-define(u32(X1,X2,X3,X4), 
	(((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)).

-include("erl_epmd.hrl").

-define(SERVER, erl_epmd).

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start() ->
    gen_server:start({local, ?SERVER}, ?MODULE, [], []).

start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

stop() ->
    gen_server:call(?SERVER, stop).

%% Lookup a node "Name" at Host
%% return {port, P, Version} | noport
%%
port_please(Node, EpmdAddr) ->
    case get_port(Node, EpmdAddr, 0, 1) of
        {port, P, ?TCP_PROTO, Version} ->
            {proto, P, Version};
        _ ->
            noport
    end.

port_please(Node, EpmdAddr, L, H) when integer(L), integer(H), 
                                       L =< H, L >= 0, H =< 16#ffff ->
    get_port(Node, EpmdAddr, L, H).

port_please(Node, EpmdAddr, V) when integer(V), V >= 0, V =< 16#ffff ->
    get_port(Node, EpmdAddr, V, V).

names(EpmdAddr) ->
    get_names(EpmdAddr).

register_node(Name, PortNo) -> %% old node
    register_node(Name, PortNo, 0, 1, ?TCP_PROTO).

register_node(Name, PortNo, V) ->
    register_node(Name, PortNo, V, V, ?TCP_PROTO).    

register_node(Name, PortNo,L,H,Proto) when integer(L), integer(H), 
                                           integer(Proto),
                                           L =< H, L >= 0, H =< 16#ffff ->
    gen_server:call(?SERVER, {register, Name, PortNo, L,H, Proto}, 15000).

%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------

init(_) ->
    {ok, #state{socket = -1}}.
	    
%%----------------------------------------------------------------------

handle_call({register, Name, PortNo, Low,High,Proto}, _From, State) ->
    case State#state.socket of
        P when P < 0 ->
            case do_register_node(Name, PortNo, Low,High,Proto) of
                {alive, Socket, Creation} ->
                    S = State#state{socket = Socket,
                                    port_no = PortNo,
                                    name = Name},
                    {reply, {ok, Creation}, S};
                Error ->
                    {reply, Error, State}
            end;
        _ ->
            {reply, {error, already_registered}, State}
    end;

handle_call(stop, _From, State) ->
    {stop, shutdown, ok, State}.

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

handle_cast(_, State) ->
    {noreply, State}.

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

handle_info({tcp_closed, Socket}, State) when State#state.socket == Socket ->
    {noreply, State#state{socket = -1}};
handle_info(_, State) ->
    {noreply, State}.

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

terminate(_, #state{socket = Socket}) when Socket > 0 ->
    close(Socket),
    ok;
terminate(_, _) ->
    ok.

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

%%
%% Epmd socket
%%
open() -> open({127,0,0,1}).  % The localhost IP address.

open(EpmdAddr) ->
    inet_tcp:connect(EpmdAddr, ?erlang_daemon_port, []).

tokens([$:|T], Ack) -> [reverse(Ack),T];
tokens([H|T], Ack)  -> tokens(T, [H|Ack]);
tokens([], Ack)     -> [reverse(Ack), []].

close(Socket) ->
    inet_tcp:close(Socket).

do_register_node_v0(NodeName, TcpPort) ->
    case open() of
	{ok, Socket} ->
	    Name = cstring(NodeName),
	    Len = 1+2+length(Name),
	    inet_tcp:send(Socket, [?int16(Len), ?EPMD_ALIVE,
				   ?int16(TcpPort), Name]),
	    wait_for_reg_reply_v0(Socket, []);
	Error ->
	    Error
    end.

do_register_node(NodeName, TcpPort, Low, High,Proto) ->
    case open() of
        {ok, Socket} ->
            Name = to_string(NodeName),
            Extra = "",
            Elen = length(Extra),
            Len = 1+2+1+1+2+2+2+length(Name)+2+Elen,
            inet_tcp:send(Socket, [?int16(Len), ?EPMD_ALIVE2_REQ,
                                   ?int16(TcpPort),
                                   $M,
                                   Proto,
                                   ?int16(Low),
                                   ?int16(High),
                                   ?int16(length(Name)),
                                   Name,
                                   ?int16(Elen),
                                   Extra]),
            case wait_for_reg_reply(Socket, []) of
                {error, epmd_close} when Low == 0 ->
                    %% could be old epmd; try old protocol
%                   erlang:display('trying old'),
                    do_register_node_v0(NodeName, TcpPort);
                Other ->
                    Other
            end;
        Error ->
            Error
    end.


%%% (When we reply 'duplicate_name', it's because it's the most likely
%%% reason; there is no interpretation of the error result code.)
wait_for_reg_reply(Socket, SoFar) ->
    receive
	{tcp, Socket, Data0} ->
	    case SoFar ++ Data0 of
		[$y, Result, A, B] ->
		    case Result of
			0 ->
			    {alive, Socket, ?u16(A, B)};
			_ ->
			    {error, duplicate_name}
		    end;
		Data when length(Data) < 4 ->
		    wait_for_reg_reply(Socket, Data);
		Garbage ->
		    {error, {garbage_from_epmd, Garbage}}
	    end;
	{tcp_closed, Socket} ->
	    {error, epmd_close}
    after 10000 ->
	    inet_tcp:close(Socket),
	    {error, no_reg_reply_from_epmd}
    end.
    
wait_for_reg_reply_v0(Socket, SoFar) ->
    receive
	{tcp, Socket, Data0} ->
	    case SoFar ++ Data0 of
		[$Y, A, B] ->
		    {alive, Socket, ?u16(A, B)};
		Data when length(Data) < 3 ->
		    wait_for_reg_reply(Socket, Data);
		Garbage ->
		    {error, {garbage_from_epmd, Garbage}}
	    end;
	{tcp_closed, Socket} ->
	    {error, duplicate_name}		% A guess -- the most likely reason.
    after 10000 ->
	    inet_tcp:close(Socket),
	    {error, no_reg_reply_from_epmd}
    end.
%%
%% Lookup a node "Name" at Host
%%
get_port_v0(Node, EpmdAddress) ->
    case open(EpmdAddress) of
	{ok, Socket} ->
	    Name = cstring(Node),
	    Len = 1+length(Name),
	    inet_tcp:send(Socket, [?int16(Len),?EPMD_PORT_PLEASE, Name]),
	    wait_for_port_reply_v0(Socket, []);
	Error -> 
	    noport
    end.

get_port(Node, EpmdAddress,L,H) ->
    case open(EpmdAddress) of
        {ok, Socket} ->
            Name = to_string(Node),
            Len = 1+length(Name),
            inet_tcp:send(Socket, [?int16(Len),?EPMD_PORT_PLEASE2_REQ, Name]),
            Reply = wait_for_port_reply(Socket, [],L,H),
            case Reply of
                closed ->
                    get_port_v0(Node, EpmdAddress);
                Other ->
                    Other
            end;
        Error -> 
            noport
    end.

wait_for_port_reply_v0(Socket, SoFar) ->
    receive
	{tcp, Socket, Data0} ->
	    case SoFar ++ Data0 of
		[A, B] ->
		    wait_for_close(Socket, {port, ?u16(A, B), 0});
		Data when length(Data) < 2 ->
		    wait_for_port_reply_v0(Socket, Data);
		Garbage ->
		    {error, {garbage_from_epmd, Garbage}}
	    end;
	{tcp_closed, Socket} ->
	    noport
    after 10000 ->
	    inet_tcp:close(Socket),
	    noport
    end.

wait_for_port_reply(Socket, SoFar,L,H) ->
    receive
        {tcp, Socket, Data0} ->
            case SoFar ++ Data0 of
                [$w, Result | Rest] ->
                    case Result of
                        0 ->
                            wait_for_port_reply_cont(Socket, Rest,L,H);
                        _ ->
                            wait_for_close(Socket, noport)
                    end;
                Data when length(Data) < 2 ->
                    wait_for_port_reply(Socket, Data,L,H);
                Garbage ->
                    {error, {garbage_from_epmd, Garbage}}
            end;
        {tcp_closed, Socket} ->
            closed
    after 10000 ->
            inet_tcp:close(Socket),
            noport
    end.


wait_for_port_reply_cont(Socket, SoFar,L,H) when length(SoFar) >= 10 ->
    wait_for_port_reply_cont2(Socket, SoFar,L,H);
wait_for_port_reply_cont(Socket, SoFar,L,H) ->
    receive
        {tcp, Socket, Data0} ->
            case SoFar ++ Data0 of
                Data when length(Data) >= 10 ->
                    wait_for_port_reply_cont2(Socket, Data,L,H);
                Data when length(Data) < 10 ->
                    wait_for_port_reply_cont(Socket, Data,L,H);
                Garbage ->
                    {error, {garbage_from_epmd, Garbage}}
            end;
        {tcp_closed, Socket} ->
            noport
    after 10000 ->
            inet_tcp:close(Socket),
            noport
    end.

wait_for_port_reply_cont2(Socket, Data,OurLow,OurHigh) ->
    [A, B, Type, Proto, HighA, HighB,
     LowA, LowB, NLenA, NLenB | Rest] = Data,
    Name = wait_for_port_reply_name(Socket,
                                    ?u16(NLenA, NLenB),
                                    Rest),
    Low = ?u16(LowA, LowB),
    High = ?u16(HighA, HighB),
    Version = select_best_version(OurLow,OurHigh,Low,High),
    {port, ?u16(A, B), Proto, Version}.

%%% Throw away the rest of the message; we won't use any of it anyway,
%%% currently.
wait_for_port_reply_name(Socket, Len, Sofar) ->
    receive
	{tcp, Socket, Data} ->
%	    io:format("data = ~p~n", Data),
	    wait_for_port_reply_name(Socket, Len, Sofar);
	{tcp_closed, Socket} ->
	    "foobar"
    end.
		    
%%% We silently assume that the low's are not greater than the high's.
%%% We should report if the intervals don't overlap.
select_best_version(L1, H1, L2, H2) when L1 > H2 ->
    0;
select_best_version(L1, H1, L2, H2) when L2 > H1 ->
    0;
select_best_version(L1, H1, L2, H2) when L2 > H1 ->
    0;
select_best_version(L1, H1, L2, H2) ->
    min(H1, H2).

min(A, B) when A < B ->
    A;
min(A, B) ->
    B.

wait_for_close(Socket, Reply) ->
    receive
	{tcp_closed, Socket} -> 
	    Reply
    after 10000 ->
	    inet_tcp:close(Socket),
	    Reply
    end.


%%
%% Creates a (flat) null terminated string from atom or list.
%%
cstring(S) when atom(S) -> cstring(atom_to_list(S));
cstring(S) when list(S) -> S ++ [0].

to_string(S) when atom(S) -> atom_to_list(S);
to_string(S) when list(S) -> S.

%%
%% Find names on epmd
%%
%%
get_names(EpmdAddress) ->
    case open(EpmdAddress) of
	{ok, Socket} ->
	    do_get_names(Socket);
	Error ->
	    {error, address}
    end.

do_get_names(Socket) ->
    inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES]),
    receive
	{tcp, Socket, [P0,P1,P2,P3|T]} ->
	    EpmdPort = ?u32(P0,P1,P2,P3),
	    if EpmdPort == ?erlang_daemon_port ->
		    names_loop(Socket, T, []);
	       true ->
		    close(Socket),
		    {error, address}
	    end;
	{tcp_closed, Socket} ->
	    {ok, []}
    end.

names_loop(Socket, Acc, Ps) ->
    receive
	{tcp, Socket, Bytes} ->
	    {NAcc, NPs} = scan_names(Acc ++ Bytes, Ps),
	    names_loop(Socket, NAcc, NPs);
	{tcp_closed, Socket} ->
	    {_, NPs} = scan_names(Acc, Ps),
	    {ok, NPs}
    end.

scan_names(Buf, Ps) ->
    case scan_line(Buf, []) of
	{Line, NBuf} ->
	    case parse_line(Line) of
		{ok, Entry} -> 
		    scan_names(NBuf, [Entry | Ps]);
		error ->
		    scan_names(NBuf, Ps)
	    end;
	[] -> {Buf, Ps}
    end.


scan_line([$\n | Buf], Line) -> {reverse(Line), Buf};
scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]);
scan_line([], _) -> [].

parse_line([$n,$a,$m,$e,$ | Buf0]) ->
    case parse_name(Buf0, []) of
	{Name, Buf1}  ->
	    case Buf1 of
		[$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] ->
		    case catch list_to_integer(Buf2) of
			{'EXIT', _} -> error;
			Port -> {ok, {Name, Port}}
		    end;
		_ -> error
	    end;
	error -> error
    end;
parse_line(_) -> error.


parse_name([$  | Buf], Name) -> {reverse(Name), Buf};
parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]);
parse_name([], Name) -> error.



