diff options
Diffstat (limited to 'lib/common_test/src/ct_telnet_client.erl')
-rw-r--r-- | lib/common_test/src/ct_telnet_client.erl | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl new file mode 100644 index 0000000000..e460a50eac --- /dev/null +++ b/lib/common_test/src/ct_telnet_client.erl @@ -0,0 +1,304 @@ +%% +%% %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. + + + + + + + + + + + + + |