%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
%%---------------------------------------------------------------
%% Basic negotiated options with Telnet (RFC 854)
%%
%% Side A request: I WILL set option Opt.
%% Side B answer: DO go ahead, or no, DON'T set it.
%%
%% Side A request: Please DO set this option.
%% Side B answer: Ok I WILL, or no, I WON'T set it.
%%
%% "Enable option" requests may be rejected.
%% "Disable option" requests must not.
%%---------------------------------------------------------------
-module(ct_telnet_client).
-export([open/1, open/2, open/3, close/1]).
-export([send_data/2, get_data/1]).
-define(DBG, false).
-define(TELNET_PORT, 23).
-define(OPEN_TIMEOUT,10000).
%% telnet control characters
-define(SE, 240).
-define(NOP, 241).
-define(DM, 242).
-define(BRK, 243).
-define(IP, 244).
-define(AO, 245).
-define(AYT, 246).
-define(EC, 247).
-define(EL, 248).
-define(GA, 249).
-define(SB, 250).
-define(WILL, 251).
-define(WONT, 252).
-define(DO, 253).
-define(DONT, 254).
-define(IAC, 255).
%% telnet options
-define(BINARY, 0).
-define(ECHO, 1).
-define(SUPPRESS_GO_AHEAD, 3).
-define(TERMINAL_TYPE, 24).
-define(WINDOW_SIZE, 31).
-record(state,{get_data}).
open(Server) ->
open(Server, ?TELNET_PORT, ?OPEN_TIMEOUT).
open(Server, Port) ->
open(Server, Port, ?OPEN_TIMEOUT).
open(Server, Port, Timeout) ->
Self = self(),
Pid = spawn(fun() -> init(Self, Server, Port, Timeout) end),
receive
{open,Pid} ->
{ok,Pid};
{Error,Pid} ->
Error
end.
close(Pid) ->
Pid ! close.
send_data(Pid, Data) ->
Pid ! {send_data, Data++"\n"},
ok.
get_data(Pid) ->
Pid ! {get_data, self()},
receive
{data,Data} ->
{ok, Data}
end.
%%%-----------------------------------------------------------------
%%% Internal functions
init(Parent, Server, Port, Timeout) ->
case gen_tcp:connect(Server, Port, [list,{packet,0}], Timeout) of
{ok,Sock} ->
dbg("Connected to: ~p\n", [Server]),
send([?IAC,?DO,?SUPPRESS_GO_AHEAD], Sock),
Parent ! {open,self()},
loop(#state{get_data=10}, Sock, []),
gen_tcp:close(Sock);
Error ->
Parent ! {Error,self()}
end.
loop(State, Sock, Acc) ->
receive
{tcp_closed,_} ->
dbg("Connection closed\n", []),
receive
{get_data,Pid} ->
Pid ! closed
after 100 ->
ok
end;
{tcp,_,Msg0} ->
dbg("tcp msg: ~p~n",[Msg0]),
Msg = check_msg(Sock,Msg0,[]),
loop(State, Sock, [Msg | Acc]);
{send_data,Data} ->
send(Data, Sock),
loop(State, Sock, Acc);
{get_data,Pid} ->
NewState =
case Acc of
[] ->
dbg("get_data nodata\n",[]),
erlang:send_after(100,self(),{get_data_delayed,Pid}),
State#state{get_data=State#state.get_data - 1};
_ ->
Pid ! {data,lists:reverse(lists:append(Acc))},
State
end,
loop(NewState, Sock, []);
{get_data_delayed,Pid} ->
NewState = case State#state.get_data of
0 ->
send([?IAC,?DO,?NOP], Sock),
dbg("delayed after 1000\n",[]),
State#state{get_data=10};
_ ->
State
end,
NewAcc =
case erlang:is_process_alive(Pid) of
true ->
Pid ! {data,lists:reverse(lists:append(Acc))},
[];
false ->
Acc
end,
loop(NewState, Sock, NewAcc);
close ->
dbg("Closing connection\n", []),
gen_tcp:close(Sock),
ok
after 1000 ->
case Acc of
[] -> % no data buffered
send([?IAC,?DO,?NOP], Sock),
dbg("after 1000\n",[]);
_ ->
true
end,
loop(State, Sock, Acc)
end.
send(Data, Sock) ->
dbg("Sending: ~p\n", [Data]),
gen_tcp:send(Sock, Data),
ok.
check_msg(Sock,[?IAC | Cs], Acc) ->
case get_cmd(Cs) of
{Cmd,Cs1} ->
dbg("Got ", []),
cmd_dbg(Cmd),
respond_cmd(Cmd, Sock),
check_msg(Sock, Cs1, Acc);
error ->
Acc
end;
check_msg(Sock,[H|T],Acc) ->
check_msg(Sock,T,[H|Acc]);
check_msg(_Sock,[],Acc) ->
Acc.
%% Positive responses (WILL and DO).
respond_cmd([?WILL,?ECHO], Sock) ->
R = [?IAC,?DO,?ECHO],
cmd_dbg(R),
gen_tcp:send(Sock, R);
respond_cmd([?DO,?ECHO], Sock) ->
R = [?IAC,?WILL,?ECHO],
cmd_dbg(R),
gen_tcp:send(Sock, R);
%% Answers from server
respond_cmd([?WILL,?SUPPRESS_GO_AHEAD], _Sock) ->
dbg("Server will suppress-go-ahead\n", []);
respond_cmd([?WONT,?SUPPRESS_GO_AHEAD], _Sock) ->
dbg("Warning! Server won't suppress-go-ahead\n", []);
respond_cmd([?DONT | _Opt], _Sock) -> % server ack?
ok;
respond_cmd([?WONT | _Opt], _Sock) -> % server ack?
ok;
%% Negative responses (WON'T and DON'T). These are default!
respond_cmd([?WILL,Opt], Sock) ->
R = [?IAC,?DONT,Opt],
cmd_dbg(R),
gen_tcp:send(Sock, R);
respond_cmd([?DO | Opt], Sock) ->
R = [?IAC,?WONT | Opt],
cmd_dbg(R),
gen_tcp:send(Sock, R);
%% Unexpected messages.
respond_cmd([Cmd | Opt], _Sock) when Cmd >= 240, Cmd =< 255 ->
dbg("Received cmd: ~w. Ignored!\n", [[Cmd | Opt]]);
respond_cmd([Cmd | Opt], _Sock) ->
dbg("WARNING: Received unknown cmd: ~w. Ignored!\n", [[Cmd | Opt]]).
get_cmd([Cmd | Rest]) when Cmd == ?SB ->
get_subcmd(Rest, []);
get_cmd([Cmd,Opt | Rest]) ->
{[Cmd,Opt], Rest};
get_cmd(_Other) ->
error.
get_subcmd([?SE | Rest], Acc) ->
{[?SE | lists:reverse(Acc)], Rest};
get_subcmd([Opt | Rest], Acc) ->
get_subcmd(Rest, [Opt | Acc]).
dbg(_Str,_Args) -> ok.
% if ?DBG -> io:format(_Str,_Args);
% true -> ok
% end.
cmd_dbg(_Cmd) -> ok.
% if ?DBG ->
% case _Cmd of
% [?IAC|Cmd1] ->
% cmd_dbg(Cmd1);
% [Ctrl|Opts] ->
% CtrlStr =
% case Ctrl of
% ?DO -> "DO";
% ?DONT -> "DONT";
% ?WILL -> "WILL";
% ?WONT -> "WONT";
% _ -> "CMD"
% end,
% Opts1 =
% case Opts of
% [Opt] -> Opt;
% _ -> Opts
% end,
% io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]);
% Any ->
% io:format("Unexpected in cmd_dbg:~n~w~n",[Any])
% end;
% true -> ok
% end.