aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2012-10-04 15:11:12 +0200
committerIngela Anderton Andin <[email protected]>2012-10-04 15:11:12 +0200
commita24c49aec663f19092cf0d2062af19523666d291 (patch)
tree54efc5788839a01e86a79987396f31c99aa03bb7 /lib
parent8bac6930f5611a4def1306504715723303517948 (diff)
parent9e7e31c9011e1a63957b48b794b0c72669c081ae (diff)
downloadotp-a24c49aec663f19092cf0d2062af19523666d291.tar.gz
otp-a24c49aec663f19092cf0d2062af19523666d291.tar.bz2
otp-a24c49aec663f19092cf0d2062af19523666d291.zip
Merge branch 'ia/ssh/sending-large-data-and-eof/OTP-10467'
* ia/ssh/sending-large-data-and-eof/OTP-10467: ssh: Make test case platform independant ssh: ssh_connection:channel_data() and send_eof() now return {error, closed} for closed or invalid channels. ssh: Ensure that all data is sent before ssh:send returns. ssh: add test suite for testing channel send/receive behavior.
Diffstat (limited to 'lib')
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml4
-rw-r--r--lib/ssh/src/ssh_connection.erl57
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl28
-rw-r--r--lib/ssh/test/Makefile4
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl312
-rw-r--r--lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key15
-rw-r--r--lib/ssh/test/ssh_echo_server.erl71
7 files changed, 452 insertions, 39 deletions
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 9942306b93..a9ae13d556 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -196,7 +196,7 @@
<name>send(ConnectionRef, ChannelId, Data, Timeout) -></name>
<name>send(ConnectionRef, ChannelId, Type, Data) -></name>
<name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
- ok | {error, timeout}</name>
+ ok | {error, timeout} | {error, closed}</name>
<fsummary>Sends channel data </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -212,7 +212,7 @@
</func>
<func>
- <name>send_eof(ConnectionRef, ChannelId) -> ok </name>
+ <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
<fsummary>Sends eof on the channel <c>ChannelId</c>. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index e3b8ebfb79..240d7f70d1 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -318,21 +318,22 @@ channel_data(ChannelId, DataType, Data,
From) ->
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} = Channel0 ->
- {SendList, Channel} = update_send_window(Channel0, DataType,
+ #channel{remote_id = Id, sent_close = false} = Channel0 ->
+ {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType,
Data, Connection),
Replies =
lists:map(fun({SendDataType, SendData}) ->
- {connection_reply, ConnectionPid,
- channel_data_msg(Id,
- SendDataType,
- SendData)}
+ {connection_reply, ConnectionPid,
+ channel_data_msg(Id,
+ SendDataType,
+ SendData)}
end, SendList),
FlowCtrlMsgs = flow_control(Replies,
- Channel#channel{flow_control = From},
+ Channel,
Cache),
{{replies, Replies ++ FlowCtrlMsgs}, Connection};
- undefined ->
+ _ ->
+ gen_server:reply(From, {error, closed}),
{noreply, Connection}
end.
@@ -386,20 +387,30 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
ConnectionPid, _) ->
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{sent_close = Closed, remote_id = RemoteId} = Channel ->
+ #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel ->
ssh_channel:cache_delete(Cache, ChannelId),
{CloseMsg, Connection} =
reply_msg(Channel, Connection0, {closed, ChannelId}),
+
+ ConnReplyMsgs =
case Closed of
- true ->
- {{replies, [CloseMsg]}, Connection};
+ true -> [];
false ->
RemoteCloseMsg = channel_close_msg(RemoteId),
- {{replies,
- [{connection_reply,
- ConnectionPid, RemoteCloseMsg},
- CloseMsg]}, Connection}
- end;
+ [{connection_reply, ConnectionPid, RemoteCloseMsg}]
+ end,
+
+ %% if there was a send() in progress, make it fail
+ SendReplyMsgs =
+ case FlowControl of
+ undefined -> [];
+ From ->
+ [{flow_control, From, {error, closed}}]
+ end,
+
+ Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs,
+ {{replies, Replies}, Connection};
+
undefined ->
{{replies, []}, Connection0}
end;
@@ -1126,13 +1137,13 @@ flow_control(Channel, Cache) ->
flow_control([], Channel, Cache) ->
ssh_channel:cache_update(Cache, Channel),
[];
-flow_control([_|_], #channel{flow_control = From} = Channel, Cache) ->
- case From of
- undefined ->
- [];
- _ ->
- [{flow_control, Cache, Channel, From, ok}]
- end.
+
+flow_control([_|_], #channel{flow_control = From,
+ send_buf = []} = Channel, Cache) when From =/= undefined ->
+ [{flow_control, Cache, Channel, From, ok}];
+flow_control(_,_,_) ->
+ [].
+
encode_pty_opts(Opts) ->
Bin = list_to_binary(encode_pty_opts2(Opts)),
diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl
index e53cd4f4f7..af521b77e4 100644
--- a/lib/ssh/src/ssh_connection_manager.erl
+++ b/lib/ssh/src/ssh_connection_manager.erl
@@ -163,7 +163,7 @@ send(ConnectionManager, ChannelId, Type, Data, Timeout) ->
call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout).
send_eof(ConnectionManager, ChannelId) ->
- cast(ConnectionManager, {eof, ChannelId}).
+ call(ConnectionManager, {eof, ChannelId}).
%%====================================================================
%% gen_server callbacks
@@ -295,6 +295,18 @@ handle_call({data, ChannelId, Type, Data}, From,
channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From,
State);
+handle_call({eof, ChannelId}, _From,
+ #state{connection = Pid, connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id, sent_close = false} ->
+ send_msg({connection_reply, Pid,
+ ssh_connection:channel_eof_msg(Id)}),
+ {reply, ok, State};
+ _ ->
+ {reply, {error,closed}, State}
+ end;
+
handle_call({connection_info, Options}, From,
#state{connection = Connection} = State) ->
ssh_connection_handler:connection_info(Connection, From, Options),
@@ -453,18 +465,6 @@ handle_cast({adjust_window, ChannelId, Bytes},
end,
{noreply, State};
-handle_cast({eof, ChannelId},
- #state{connection = Pid, connection_state =
- #connection{channel_cache = Cache}} = State) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} ->
- send_msg({connection_reply, Pid,
- ssh_connection:channel_eof_msg(Id)}),
- {noreply, State};
- undefined ->
- {noreply, State}
- end;
-
handle_cast({success, ChannelId}, #state{connection = Pid} = State) ->
Msg = ssh_connection:channel_success_msg(ChannelId),
send_msg({connection_reply, Pid, Msg}),
@@ -614,6 +614,8 @@ do_send_msg({connection_reply, Pid, Data}) ->
ssh_connection_handler:send(Pid, Msg);
do_send_msg({flow_control, Cache, Channel, From, Msg}) ->
ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
+ gen_server:reply(From, Msg);
+do_send_msg({flow_control, From, Msg}) ->
gen_server:reply(From, Msg).
handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From,
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 25072688ad..f5db31baee 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -36,7 +36,9 @@ MODULES= \
ssh_to_openssh_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
- ssh_sftpd_erlclient_SUITE
+ ssh_sftpd_erlclient_SUITE \
+ ssh_connection_SUITE \
+ ssh_echo_server
HRL_FILES_NEEDED_IN_TEST= \
$(ERL_TOP)/lib/ssh/src/ssh.hrl \
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
new file mode 100644
index 0000000000..43a899f974
--- /dev/null
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -0,0 +1,312 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2012. 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%
+%%
+
+%%
+-module(ssh_connection_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-compile(export_all).
+
+-define(SSH_DEFAULT_PORT, 22).
+-define(EXEC_TIMEOUT, 10000).
+
+%%--------------------------------------------------------------------
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ {group, erlang_client},
+ interrupted_send
+ ].
+groups() ->
+ [{erlang_client, [], [simple_exec,
+ small_cat,
+ big_cat,
+ send_after_exit
+ ]}].
+
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ Config;
+ _Else ->
+ {skip, "Crypto could not be started!"}
+ end.
+
+end_per_suite(_Config) ->
+ crypto:stop(),
+ ok.
+%%--------------------------------------------------------------------
+init_per_group(erlang_client, Config) ->
+ case gen_tcp:connect("localhost", 22, []) of
+ {error,econnrefused} ->
+ {skip,"No openssh deamon"};
+ _ ->
+ Config
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+end_per_testcase(_Config) ->
+ ssh:stop(),
+ ok.
+
+%%% TEST cases starts here.
+%%--------------------------------------------------------------------
+simple_exec(doc) ->
+ ["Simple openssh connectivity test for ssh_connection:exec"];
+
+simple_exec(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "echo testing", infinity),
+
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+small_cat(doc) ->
+ ["Use 'cat' to echo small data block back to us."];
+
+small_cat(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "cat", infinity),
+
+ Data = <<"I like spaghetti squash">>,
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+big_cat(doc) ->
+ ["Use 'cat' to echo large data block back to us."];
+
+big_cat(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "cat", infinity),
+
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
+
+ test_server:format("sending ~p byte binary~n",[size(Data)]),
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% collect echoed data until eof
+ case big_cat_rx(ConnectionRef, ChannelId0) of
+ {ok, Data} ->
+ ok;
+ {ok, Other} ->
+ case size(Data) =:= size(Other) of
+ true ->
+ test_server:format("received and sent data are same"
+ "size but do not match~n",[]);
+ false ->
+ test_server:format("sent ~p but only received ~p~n",
+ [size(Data), size(Other)])
+ end,
+ ct:fail(receive_data_mismatch);
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages (eof already consumed)
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+big_cat_rx(ConnectionRef, ChannelId) ->
+ big_cat_rx(ConnectionRef, ChannelId, []).
+
+big_cat_rx(ConnectionRef, ChannelId, Acc) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
+ %% window was pre-adjusted, don't adjust again here
+ big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ {ok, iolist_to_binary(lists:reverse(Acc))}
+ after ?EXEC_TIMEOUT ->
+ timeout
+ end.
+
+%%--------------------------------------------------------------------
+send_after_exit(doc) ->
+ ["Send channel data after the channel has been closed."];
+
+send_after_exit(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ %% Shell command "false" will exit immediately
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "false", infinity),
+
+ timer:sleep(2000), %% Allow incoming eof/close/exit_status ssh messages to be processed
+
+ Data = <<"I like spaghetti squash">>,
+ case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of
+ {error, closed} -> ok;
+ ok ->
+ ct:fail({expected,{error,closed}});
+ {error, timeout} ->
+ ct:fail({expected,{error,closed}});
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, _}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+%%--------------------------------------------------------------------
+interrupted_send(doc) ->
+ ["Use a subsystem that echos n char and then sends eof to cause a channel exit partway through a large send."];
+
+interrupted_send(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]),
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity),
+
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% expect remote end to send us 4MB back
+ <<ExpectedData:4000000/binary, _/binary>> = Data,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId, size(ExpectedData) + 1),
+
+ case ssh_connection:send(ConnectionRef, ChannelId, Data, 10000) of
+ {error, closed} ->
+ ok;
+ Msg ->
+ ct:fail({expected,{error,closed}, got, Msg})
+ end,
+ receive_data(ExpectedData, ConnectionRef, ChannelId),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+%% Internal funtions ------------------------------------------------------------------
+
+receive_data(ExpectedData, ConnectionRef, ChannelId) ->
+ ExpectedData = collect_data(ConnectionRef, ChannelId).
+
+collect_data(ConnectionRef, ChannelId) ->
+ collect_data(ConnectionRef, ChannelId, []).
+
+collect_data(ConnectionRef, ChannelId, Acc) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ collect_data(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ iolist_to_binary(lists:reverse(Acc))
+ after 5000 ->
+ timeout
+ end.
diff --git a/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..6ae7ee023d
--- /dev/null
+++ b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_echo_server.erl b/lib/ssh/test/ssh_echo_server.erl
new file mode 100644
index 0000000000..739aabe6fb
--- /dev/null
+++ b/lib/ssh/test/ssh_echo_server.erl
@@ -0,0 +1,71 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2012. 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%
+%%
+
+%%
+
+%%% Description: Example ssh server
+-module(ssh_echo_server).
+-behaviour(ssh_channel).
+-record(state, {
+ n,
+ id,
+ cm
+ }).
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([N]) ->
+ {ok, #state{n = N}}.
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}}.
+
+handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State) ->
+ M = N - size(Data),
+ case M > 0 of
+ true ->
+ ssh_connection:send(CM, ChannelId, Data),
+ {ok, State#state{n = M}};
+ false ->
+ <<SendData:N/binary, _/binary>> = Data,
+ ssh_connection:send(CM, ChannelId, SendData),
+ ssh_connection:send_eof(CM, ChannelId),
+ {stop, ChannelId, State}
+ end;
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 1, Data}}, State) ->
+ error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}},
+ State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) ->
+ {stop, ChannelId, State}.
+
+terminate(_Reason, _State) ->
+ ok.