aboutsummaryrefslogblamecommitdiffstats
path: root/lib/kernel/test/gen_sctp_SUITE.erl
blob: f422ffe442fe797d398eb5793d78e8d105722e43 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                   
  
                                                        
  




                                                                      
  



                                                                         
  



                        
                                                    



                                             
                                 


                                                   


                                                                
                                                               
                                                   
 
                                         
 
         
                                                         
                                                                     
                                             





















                                                        
           

                                    
           
 



                                                        
                                  




                                     



                                                                 
















                                                                            
                                                      


















































































































                                                                            


                                                               








































                                                                     










                                                              







                                                                  








































































































                                                                             







                                                                            




























                                                                      




                                           

      

































































































                                                         
                                                      









                                                                












































                                                                              





                                        

                         






                                                               
              







                                                        






















































                                                                                
 


























































































                                                                            


























                                                                    





























































































































































































                                                                                
%% 
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2011. 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(gen_sctp_SUITE).

-include_lib("test_server/include/test_server.hrl").
-include_lib("kernel/include/inet_sctp.hrl").

%%-compile(export_all).

-export([all/0, suite/0,groups/0,
	 init_per_suite/1,end_per_suite/1,
	 init_per_group/2,end_per_group/2,
	 init_per_testcase/2, end_per_testcase/2]).
-export(
   [basic/1,
    api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1,
    xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1,
    basic_stream/1, xfer_stream_min/1, peeloff/1]).

suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    [basic, api_open_close, api_listen, api_connect_init,
     api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
     basic_stream, xfer_stream_min, peeloff].

groups() -> 
    [].

init_per_suite(Config) ->
    try gen_sctp:open() of
	{ok,Socket} ->
	    gen_sctp:close(Socket),
	    [];
	_ ->
	    []
    catch
	error:badarg ->
	    {skip,"SCTP not supported on this machine"};
	_:_ ->
	    Config
    end.

end_per_suite(_Conifig) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


init_per_testcase(_Func, Config) ->
    Dog = test_server:timetrap(test_server:seconds(15)),
    [{watchdog, Dog}|Config].
end_per_testcase(_Func, Config) ->
    Dog = ?config(watchdog, Config),
    test_server:timetrap_cancel(Dog).



-define(LOGVAR(Var), begin io:format(??Var" = ~p~n", [Var]) end).



basic(doc) ->
    "Hello world";
basic(suite) ->
    [];
basic(Config) when is_list(Config) ->
    ?line {ok,S} = gen_sctp:open(),
    ?line ok = gen_sctp:close(S),
    ok.

xfer_min(doc) ->
    "Minimal data transfer";
xfer_min(suite) ->
    [];
xfer_min(Config) when is_list(Config) ->
    ?line Stream = 0,
    ?line Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
    ?line Loopback = {127,0,0,1},
    ?line {ok,Sb} = gen_sctp:open([{type,seqpacket}]),
    ?line {ok,Pb} = inet:port(Sb),
    ?line ok = gen_sctp:listen(Sb, true),
    
    ?line {ok,Sa} = gen_sctp:open(),
    ?line {ok,Pa} = inet:port(Sa),
    ?line {ok,#sctp_assoc_change{state=comm_up,
				 error=0,
				 outbound_streams=SaOutboundStreams,
				 inbound_streams=SaInboundStreams,
				 assoc_id=SaAssocId}=SaAssocChange} =
	gen_sctp:connect(Sa, Loopback, Pb, []),
    ?line {ok,{Loopback,
	       Pa,[],
	       #sctp_assoc_change{state=comm_up,
				  error=0,
				  outbound_streams=SbOutboundStreams,
				  inbound_streams=SbInboundStreams,
				  assoc_id=SbAssocId}}} =
	gen_sctp:recv(Sb, infinity),
    ?line SaOutboundStreams = SbInboundStreams,
    ?line SbOutboundStreams = SaInboundStreams,
    ?line ok = gen_sctp:send(Sa, SaAssocId, 0, Data),
    ?line case gen_sctp:recv(Sb, infinity) of
	      {ok,{Loopback,
		   Pa,
		   [#sctp_sndrcvinfo{stream=Stream,
				     assoc_id=SbAssocId}],
		   Data}} -> ok;
	      {ok,{Loopback,
		   Pa,[],
		   #sctp_paddr_change{addr = {Loopback,_},
				      state = addr_available,
				      error = 0,
				      assoc_id = SbAssocId}}} ->
		  {ok,{Loopback,
		       Pa,
		       [#sctp_sndrcvinfo{stream=Stream,
					 assoc_id=SbAssocId}],
		       Data}} =	gen_sctp:recv(Sb, infinity)
	  end,
    ?line ok = gen_sctp:send(Sb, SbAssocId, 0, Data),
    ?line {ok,{Loopback,
	       Pb,
	       [#sctp_sndrcvinfo{stream=Stream,
				 assoc_id=SaAssocId}],
	       Data}} =
	gen_sctp:recv(Sa, infinity),
    %%
    ?line ok = gen_sctp:eof(Sa, SaAssocChange),
    ?line {ok,{Loopback,
 	       Pa,[],
 	       #sctp_shutdown_event{assoc_id=SbAssocId}}} =
 	gen_sctp:recv(Sb, infinity),
    ?line {ok,{Loopback,
 	       Pb,[],
 	       #sctp_assoc_change{state=shutdown_comp,
 				  error=0,
 				  assoc_id=SaAssocId}}} =
 	gen_sctp:recv(Sa, infinity),
    ?line {ok,{Loopback,
 	       Pa,[],
 	       #sctp_assoc_change{state=shutdown_comp,
 				  error=0,
 				  assoc_id=SbAssocId}}} =
 	gen_sctp:recv(Sb, infinity),
    ?line ok = gen_sctp:close(Sa),
    ?line ok = gen_sctp:close(Sb),
    
    ?line receive
	      Msg -> test_server:fail({received,Msg})
	  after 17 -> ok
	  end,
    ok.

xfer_active(doc) ->
    "Minimal data transfer in active mode";
xfer_active(suite) ->
    [];
xfer_active(Config) when is_list(Config) ->
    ?line Timeout = 2000,
    ?line Stream = 0,
    ?line Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
    ?line Loopback = {127,0,0,1},
    ?line {ok,Sb} = gen_sctp:open([{active,true}]),
    ?line {ok,Pb} = inet:port(Sb),
    ?line ok = gen_sctp:listen(Sb, true),
    
    ?line {ok,Sa} = gen_sctp:open([{active,true}]),
    ?line {ok,Pa} = inet:port(Sa),
    ?line {ok,#sctp_assoc_change{state=comm_up,
				 error=0,
				 outbound_streams=SaOutboundStreams,
				 inbound_streams=SaInboundStreams,
				 assoc_id=SaAssocId}=SaAssocChange} =
	gen_sctp:connect(Sa, Loopback, Pb, []),
    ?line io:format("Sa=~p, Pa=~p, Sb=~p, Pb=~p, SaAssocId=~p, "
		    "SaOutboundStreams=~p, SaInboundStreams=~p~n",
		    [Sa,Pa,Sb,Pb,SaAssocId,
		     SaOutboundStreams,SaInboundStreams]),
    ?line SbAssocId = 
	receive
	    {sctp,Sb,Loopback,Pa,
	     {[],
	      #sctp_assoc_change{state=comm_up,
				 error=0,
				   outbound_streams=SbOutboundStreams,
				 inbound_streams=SbInboundStreams,
				 assoc_id=SBAI}}} ->
		?line SaOutboundStreams = SbInboundStreams,
		?line  SaInboundStreams = SbOutboundStreams,
		SBAI
	after Timeout ->
		?line test_server:fail({unexpected,flush()})
	end,
    ?line io:format("SbAssocId=~p~n", [SbAssocId]),
    ?line ok =
	do_from_other_process(
	  fun () -> gen_sctp:send(Sa, SaAssocId, 0, Data) end),
    ?line receive
	      {sctp,Sb,Loopback,Pa,
	       {[#sctp_sndrcvinfo{stream=Stream,
				  assoc_id=SbAssocId}],
		Data}} -> ok;
	      {sctp,Sb,Loopback,Pa,
	       {[],
		#sctp_paddr_change{addr = {Loopback,_},
				   state = addr_available,
				   error = 0,
				   assoc_id = SbAssocId}}} ->
		  ?line receive
			    {sctp,Sb,Loopback,Pa,
			     {[#sctp_sndrcvinfo{stream=Stream,
						assoc_id=SbAssocId}],
			      Data}} -> ok
			end
	  after Timeout ->
		  ?line test_server:fail({unexpected,flush()})
	  end,
    ?line ok = gen_sctp:send(Sb, SbAssocId, 0, Data),
    ?line receive
	      {sctp,Sa,Loopback,Pb,
	       {[#sctp_sndrcvinfo{stream=Stream,
				  assoc_id=SaAssocId}],
		Data}} -> ok
	  after Timeout ->
		  ?line test_server:fail({unexpected,flush()})
	  end,
    %%
    ?line ok = gen_sctp:abort(Sa, SaAssocChange),
    ?line receive
	      {sctp,Sb,Loopback,Pa,
	       {[],
		#sctp_assoc_change{state=comm_lost,
				   assoc_id=SbAssocId}}} -> ok
	  after Timeout ->
		  ?line test_server:fail({unexpected,flush()})
	  end,
    ?line ok = gen_sctp:close(Sb),
    ?line receive
              {sctp,Sa,Loopback,Pb,
               {[],
                #sctp_assoc_change{state=comm_lost,
                                   assoc_id=SaAssocId}}} -> ok
          after Timeout ->
                  ?line test_server:fail({unexpected,flush()})
          end,
    ?line receive
              {sctp_error,Sa,enotconn} -> ok % Solaris
          after 17 -> ok %% Only happens on Solaris
          end,
    ?line ok = gen_sctp:close(Sa),
    %%
    ?line receive
	      Msg -> test_server:fail({unexpected,[Msg]++flush()})
	  after 17 -> ok
	  end,
    ok.

def_sndrcvinfo(doc) ->
    "Test that #sctp_sndrcvinfo{} parameters set on a socket "
	"are used by gen_sctp:send/4";
def_sndrcvinfo(suite) ->
    [];
def_sndrcvinfo(Config) when is_list(Config) ->
    ?line Loopback = {127,0,0,1},
    ?line Data = <<"What goes up, must come down.">>,
    %%
    ?line S1 =
	ok(gen_sctp:open(
	     0, [{sctp_default_send_param,#sctp_sndrcvinfo{ppid=17}}])),
    ?LOGVAR(S1),
    ?line P1 =
	ok(inet:port(S1)),
    ?LOGVAR(P1),
    ?line #sctp_sndrcvinfo{ppid=17, context=0, timetolive=0, assoc_id=0} =
	getopt(S1, sctp_default_send_param),
    ?line ok =
	gen_sctp:listen(S1, true),
    %%
    ?line S2 =
	ok(gen_sctp:open()),
    ?LOGVAR(S2),
    ?line P2 =
	ok(inet:port(S2)),
    ?LOGVAR(P2),
    ?line #sctp_sndrcvinfo{ppid=0, context=0, timetolive=0, assoc_id=0} =
	getopt(S2, sctp_default_send_param),
    %%
    ?line #sctp_assoc_change{
       state=comm_up,
       error=0,
       assoc_id=S2AssocId} = S2AssocChange =
	ok(gen_sctp:connect(S2, Loopback, P1, [])),
    ?LOGVAR(S2AssocChange),
    ?line case ok(gen_sctp:recv(S1)) of
	      {Loopback, P2,[],
	       #sctp_assoc_change{
			      state=comm_up,
			      error=0,
			      assoc_id=S1AssocId}} ->
		  ?LOGVAR(S1AssocId)
	  end,
    ?line #sctp_sndrcvinfo{
       ppid=17, context=0, timetolive=0, assoc_id=S1AssocId} =
	getopt(
	  S1, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S1AssocId}),
    ?line #sctp_sndrcvinfo{
       ppid=0, context=0, timetolive=0, assoc_id=S2AssocId} =
	getopt(
	  S2, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S2AssocId}),
    %%
    ?line ok =
	gen_sctp:send(S1, S1AssocId, 1, <<"1: ",Data/binary>>),
    ?line case ok(gen_sctp:recv(S2)) of
	      {Loopback,P1,
	       [#sctp_sndrcvinfo{
		   stream=1, ppid=17, context=0, assoc_id=S2AssocId}],
	       <<"1: ",Data/binary>>} -> ok
	  end,
    %%
    ?line ok =
	setopt(
	  S1, sctp_default_send_param, #sctp_sndrcvinfo{ppid=18}),
    ?line ok =
	setopt(
	  S1, sctp_default_send_param,
	  #sctp_sndrcvinfo{ppid=19, assoc_id=S1AssocId}),
    ?line #sctp_sndrcvinfo{
       ppid=18, context=0, timetolive=0, assoc_id=0} =
	getopt(S1, sctp_default_send_param),
    ?line #sctp_sndrcvinfo{
       ppid=19, context=0, timetolive=0, assoc_id=S1AssocId} =
	getopt(
	  S1, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S1AssocId}),
    %%
    ?line ok =
	gen_sctp:send(S1, S1AssocId, 0, <<"2: ",Data/binary>>),
    ?line case ok(gen_sctp:recv(S2)) of
	      {Loopback,P1,
	       [#sctp_sndrcvinfo{
		   stream=0, ppid=19, context=0, assoc_id=S2AssocId}],
	       <<"2: ",Data/binary>>} -> ok
	  end,
    ?line ok =
	gen_sctp:send(S2, S2AssocChange, 1, <<"3: ",Data/binary>>),
    ?line case ok(gen_sctp:recv(S1)) of
	      {Loopback,P2,
	       [#sctp_sndrcvinfo{
		   stream=1, ppid=0, context=0, assoc_id=S1AssocId}],
	       <<"3: ",Data/binary>>} -> ok;
	      {Loopback,P2,[],
	       #sctp_paddr_change{
			  addr={Loopback,_}, state=addr_available,
			  error=0, assoc_id=S1AssocId}} ->
		  ?line case ok(gen_sctp:recv(S1)) of
			    {Loopback,P2,
			     [#sctp_sndrcvinfo{
				 stream=1, ppid=0, context=0,
				 assoc_id=S1AssocId}],
			     <<"3: ",Data/binary>>} -> ok
			end
	  end,
    ?line ok =
	do_from_other_process(
	  fun () ->
		  gen_sctp:send(
		    S2,
		    #sctp_sndrcvinfo{stream=0, ppid=20, assoc_id=S2AssocId},
		    <<"4: ",Data/binary>>)
	  end),
    ?line case ok(do_from_other_process(fun() -> gen_sctp:recv(S1) end)) of
	      {Loopback,P2,
	       [#sctp_sndrcvinfo{
		   stream=0, ppid=20, context=0, assoc_id=S1AssocId}],
	       <<"4: ",Data/binary>>} -> ok
	  end,
    %%
    ?line ok =
	gen_sctp:close(S1),
    ?line ok =
	gen_sctp:close(S2),
    ?line receive
	      Msg ->
		  test_server:fail({received,Msg})
	  after 17 -> ok
	  end,
    ok.

getopt(S, Opt) ->
    {ok,[{Opt,Val}]} = inet:getopts(S, [Opt]),
    Val.

getopt(S, Opt, Param) ->
    {ok,[{Opt,Val}]} = inet:getopts(S, [{Opt,Param}]),
    Val.

setopt(S, Opt, Val) ->
    inet:setopts(S, [{Opt,Val}]).

ok({ok,X}) ->
    io:format("OK[~w]: ~p~n", [self(),X]),
    X.

log(X) ->
    io:format("LOG[~w]: ~p~n", [self(),X]),
    X.

flush() ->
    receive
	Msg ->
	    [Msg|flush()]
    after 17 ->
	    []
    end.

api_open_close(doc) ->
    "Test the API function open/1,2 and close/1";
api_open_close(suite) ->
    [];
api_open_close(Config) when is_list(Config) ->
    ?line {ok,S1} = gen_sctp:open(0),
    ?line {ok,P}  = inet:port(S1),
    ?line ok      = gen_sctp:close(S1),
    
    ?line {ok,S2} = gen_sctp:open(P),
    ?line {ok,P}  = inet:port(S2),
    ?line ok      = gen_sctp:close(S2),
    
    ?line {ok,S3} = gen_sctp:open([{port,P}]),
    ?line {ok,P}  = inet:port(S3),
    ?line ok      = gen_sctp:close(S3),
    
    ?line {ok,S4} = gen_sctp:open(P, []),
    ?line {ok,P}  = inet:port(S4),
    ?line ok      = gen_sctp:close(S4),
    
    ?line {ok,S5} = gen_sctp:open(P, [{ifaddr,any}]),
    ?line {ok,P}  = inet:port(S5),
    ?line ok      = gen_sctp:close(S5),

    ?line ok      = gen_sctp:close(S5),
    
    ?line try gen_sctp:close(0)
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open({})
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(-1)
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(65536)
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(make_ref(), [])
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(0, {})
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(0, [make_ref()])
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open([{invalid_option,0}])
	  catch error:badarg -> ok
	  end,
    
    ?line try gen_sctp:open(0, [{mode,invalid_mode}])
	  catch error:badarg -> ok
	  end,
    ok.

api_listen(doc) ->
    "Test the API function listen/2";
api_listen(suite) ->
    [];
api_listen(Config) when is_list(Config) ->
    ?line Localhost = {127,0,0,1},
    
    ?line try gen_sctp:listen(0, true)
	  catch error:badarg -> ok
	  end,
    
    ?line {ok,S} = gen_sctp:open(),
    ?line {ok,Pb} = inet:port(S),
    ?line try gen_sctp:listen(S, not_allowed_for_listen)
	  catch error:badarg -> ok
	  end,
    ?line ok = gen_sctp:close(S),
    ?line {error,closed} = gen_sctp:listen(S, true),
    
    ?line {ok,Sb} = gen_sctp:open(Pb),
    ?line {ok,Sa} = gen_sctp:open(),
    ?line case gen_sctp:connect(Sa, localhost, Pb, []) of
	      {error,econnrefused} ->
		  ?line {ok,{Localhost,
			     Pb,[],
			     #sctp_assoc_change{
				  state=comm_lost}}} =
		      gen_sctp:recv(Sa, infinity);
	      {error,#sctp_assoc_change{state=cant_assoc}} -> ok
	  end,
    ?line ok = gen_sctp:listen(Sb, true),
    ?line {ok,#sctp_assoc_change{state=comm_up,
				 error=0}} =
	gen_sctp:connect(Sa, localhost, Pb, []),
    ?line ok = gen_sctp:close(Sa),
    ?line ok = gen_sctp:close(Sb),
    ok.

api_connect_init(doc) ->
    "Test the API function connect_init/4";
api_connect_init(suite) ->
    [];
api_connect_init(Config) when is_list(Config) ->
    ?line Localhost = {127,0,0,1},

    ?line {ok,S} = gen_sctp:open(),
    ?line {ok,Pb} = inet:port(S),
    ?line try gen_sctp:connect_init(S, Localhost, not_allowed_for_port, [])
	  catch error:badarg -> ok
	  end,
    ?line try gen_sctp:connect_init(S, Localhost, 12345, not_allowed_for_opts)
	  catch error:badarg -> ok
	  end,
    ?line ok = gen_sctp:close(S),
    ?line {error,closed} = gen_sctp:connect_init(S, Localhost, 12345, []),

    ?line {ok,Sb} = gen_sctp:open(Pb),
    ?line {ok,Sa} = gen_sctp:open(),
    ?line case gen_sctp:connect_init(Sa, localhost, Pb, []) of
	      {error,econnrefused} ->
		  ?line {ok,{Localhost,
			     Pb,[],
			     #sctp_assoc_change{state=comm_lost}}} =
		      gen_sctp:recv(Sa, infinity);
	      ok ->
		  ?line {ok,{Localhost,
			     Pb,[],
			     #sctp_assoc_change{state=cant_assoc}}} =
		      gen_sctp:recv(Sa, infinity)
	  end,
    ?line ok = gen_sctp:listen(Sb, true),
    ?line case gen_sctp:connect_init(Sa, localhost, Pb, []) of
	      ok ->
		  ?line {ok,{Localhost,
			     Pb,[],
			     #sctp_assoc_change{
				  state = comm_up}}} =
		      gen_sctp:recv(Sa, infinity)
	  end,
    ?line ok = gen_sctp:close(Sa),
    ?line ok = gen_sctp:close(Sb),
    ok.

api_opts(doc) ->
    "Test socket options";
api_opts(suite) ->
    [];
api_opts(Config) when is_list(Config) ->
    ?line Sndbuf = 32768,
    ?line Recbuf = 65536,
    ?line {ok,S} = gen_sctp:open(0),
    ?line OSType = os:type(),
    ?line case {inet:setopts(S, [{linger,{true,2}}]),OSType} of
              {ok,_} ->
                  ok;
              {{error,einval},{unix,sunos}} ->
                  ok
          end,
    ?line ok = inet:setopts(S, [{sndbuf,Sndbuf}]),
    ?line ok = inet:setopts(S, [{recbuf,Recbuf}]),
    ?line case inet:getopts(S, [sndbuf]) of
	      {ok,[{sndbuf,SB}]} when SB >= Sndbuf -> ok
	  end,
    ?line case inet:getopts(S, [recbuf]) of
	      {ok,[{recbuf,RB}]} when RB >= Recbuf -> ok
	  end.

implicit_inet6(Config) when is_list(Config) ->
    ?line Hostname = ok(inet:gethostname()),
    ?line
	case gen_sctp:open(0, [inet6]) of
	    {ok,S1} ->
		?line
		    case inet:getaddr(Hostname, inet6) of
			{ok,Host} ->
			    ?line Loopback = {0,0,0,0,0,0,0,1},
			    ?line io:format("~s ~p~n", ["Loopback",Loopback]),
			    ?line implicit_inet6(S1, Loopback),
			    ?line ok = gen_sctp:close(S1),
			    %%
			    ?line Localhost =
				ok(inet:getaddr("localhost", inet6)),
			    ?line io:format("~s ~p~n", ["localhost",Localhost]),
			    ?line S2 =
				ok(gen_sctp:open(0, [{ip,Localhost}])),
			    ?line implicit_inet6(S2, Localhost),
			    ?line ok = gen_sctp:close(S2),
			    %%
			    ?line io:format("~s ~p~n", [Hostname,Host]),
			    ?line S3 =
				ok(gen_sctp:open(0, [{ifaddr,Host}])),
			    ?line implicit_inet6(S3, Host),
			    ?line ok = gen_sctp:close(S1);
			{error,eafnosupport} ->
			    ?line ok = gen_sctp:close(S1),
			    {skip,"Can not look up IPv6 address"}
		    end;
	    _ ->
		{skip,"IPv6 not supported"}
	end.

implicit_inet6(S1, Addr) ->
    ?line ok = gen_sctp:listen(S1, true),
    ?line P1 = ok(inet:port(S1)),
    ?line S2 = ok(gen_sctp:open(0, [inet6])),
    ?line P2 = ok(inet:port(S2)),
    ?line #sctp_assoc_change{state=comm_up} =
	ok(gen_sctp:connect(S2, Addr, P1, [])),
    ?line case ok(gen_sctp:recv(S1)) of
	      {Addr,P2,[],#sctp_assoc_change{state=comm_up}} ->
		  ok
	  end,
    ?line case ok(inet:sockname(S1)) of
	      {Addr,P1} -> ok;
	      {{0,0,0,0,0,0,0,0},P1} -> ok
	  end,
    ?line case ok(inet:sockname(S2)) of
	      {Addr,P2} -> ok;
	      {{0,0,0,0,0,0,0,0},P2} -> ok
	  end,
    ?line ok = gen_sctp:close(S2).

basic_stream(doc) ->
    "Hello world stream socket";
basic_stream(suite) ->
    [];
basic_stream(Config) when is_list(Config) ->
    ?line {ok,S} = gen_sctp:open([{type,stream}]),
    ?line ok = gen_sctp:listen(S, true),
    ?line ok =
	do_from_other_process(
	  fun () -> gen_sctp:listen(S, 10) end),
    ?line ok = gen_sctp:close(S),
    ok.

xfer_stream_min(doc) ->
    "Minimal data transfer";
xfer_stream_min(suite) ->
    [];
xfer_stream_min(Config) when is_list(Config) ->
    ?line Stream = 0,
    ?line Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
    ?line Loopback = {127,0,0,1},
    ?line {ok,Sb} = gen_sctp:open([{type,seqpacket}]),
    ?line {ok,Pb} = inet:port(Sb),
    ?line ok = gen_sctp:listen(Sb, true),

    ?line {ok,Sa} = gen_sctp:open([{type,stream}]),
    ?line {ok,Pa} = inet:port(Sa),
    ?line {ok,#sctp_assoc_change{state=comm_up,
				 error=0,
				 outbound_streams=SaOutboundStreams,
				 inbound_streams=SaInboundStreams,
				 assoc_id=SaAssocId}} =
	gen_sctp:connect(Sa, Loopback, Pb, []),
    ?line {ok,{Loopback,
	       Pa,[],
	       #sctp_assoc_change{state=comm_up,
				  error=0,
				  outbound_streams=SbOutboundStreams,
				  inbound_streams=SbInboundStreams,
				  assoc_id=SbAssocId}}} =
	gen_sctp:recv(Sb, infinity),
    ?line SaOutboundStreams = SbInboundStreams,
    ?line SbOutboundStreams = SaInboundStreams,
    ?line ok = gen_sctp:send(Sa, SaAssocId, 0, Data),
    ?line case gen_sctp:recv(Sb, infinity) of
	      {ok,{Loopback,
		   Pa,
		   [#sctp_sndrcvinfo{stream=Stream,
				     assoc_id=SbAssocId}],
		   Data}} -> ok;
	      {ok,{Loopback,
		   Pa,[],
		   #sctp_paddr_change{addr = {Loopback,_},
				      state = addr_available,
				      error = 0,
				      assoc_id = SbAssocId}}} ->
		  {ok,{Loopback,
		       Pa,
		       [#sctp_sndrcvinfo{stream=Stream,
					 assoc_id=SbAssocId}],
		       Data}} =	gen_sctp:recv(Sb, infinity)
	  end,
    ?line ok =
	do_from_other_process(
	  fun () -> gen_sctp:send(Sb, SbAssocId, 0, Data) end),
    ?line {ok,{Loopback,
	       Pb,
	       [#sctp_sndrcvinfo{stream=Stream,
				 assoc_id=SaAssocId}],
	       Data}} =
	gen_sctp:recv(Sa, infinity),
    %%
    ?line ok = gen_sctp:close(Sa),
    ?line {ok,{Loopback,
	       Pa,[],
	       #sctp_shutdown_event{assoc_id=SbAssocId}}} =
	gen_sctp:recv(Sb, infinity),
    ?line {ok,{Loopback,
	       Pa,[],
	       #sctp_assoc_change{state=shutdown_comp,
				  error=0,
				  assoc_id=SbAssocId}}} =
	gen_sctp:recv(Sb, infinity),
    ?line ok = gen_sctp:close(Sb),

    ?line receive
	      Msg -> test_server:fail({received,Msg})
	  after 17 -> ok
	  end,
    ok.



do_from_other_process(Fun) ->
    Parent = self(),
    Ref = make_ref(),
    Child =
	spawn(fun () ->
		      try Fun() of
			  Result ->
			      Parent ! {Ref,Result}
		      catch
			  Class:Reason ->
			      Stacktrace = erlang:get_stacktrace(),
			      Parent ! {Ref,Class,Reason,Stacktrace}
		      end
	      end),
    Mref = erlang:monitor(process, Child),
    receive
	{Ref,Result} ->
	    receive {'DOWN',Mref,_,_,_} -> Result end;
	{Ref,Class,Reason,Stacktrace} ->
	    receive {'DOWN',Mref,_,_,_} ->
		    erlang:raise(Class, Reason, Stacktrace)
	    end;
	{'DOWN',Mref,_,_,Reason} ->
	    erlang:exit(Reason)
    end.



peeloff(doc) ->
    "Peel off an SCTP stream socket";
peeloff(suite) ->
    [];
peeloff(Config) when is_list(Config) ->
    ?line Addr = {127,0,0,1},
    ?line Stream = 0,
    ?line Timeout = 333,
    ?line S1 = socket_start(Addr, Timeout),
    ?line P1 = socket_call(S1, port),
    ?line Socket1 = socket_call(S1, socket),
    ?line ok = socket_call(S1, {listen,true}),
    ?line S2 = socket_start(Addr, Timeout),
    ?line P2 = socket_call(S2, port),
    ?line Socket2 = socket_call(S2, socket),
    %%
    ?line H_a = socket_req(S1, recv_assoc),
    ?line {S2Ai,Sa,Sb} = socket_call(S2, {connect,Addr,P1,[]}),
    ?line {S1Ai,Sb,Sa,Addr,P2} = socket_resp(H_a),
    %%
    ?line H_b = socket_req(S1, recv),
    ?line ok = socket_call(S2, {send,S2Ai,Stream,<<"Data H_b">>}),
    ?line {Addr,P2,S1Ai,Stream,<<"Data H_b">>} = socket_resp(H_b),
    ?line H_c = socket_req(S1, {recv,Socket2}),
    ?line ok =
	socket_call(S2, {send,Socket1,S1Ai,Stream,<<"Data H_c">>}),
    ?line {Addr,P1,S2Ai,Stream,<<"Data H_c">>} = socket_resp(H_c),
    %%
    ?line S3 = socket_peeloff(Socket1, S1Ai, Timeout),
    ?line P3 = socket_call(S3, port),
    ?line Socket3 = socket_call(S3, socket),
    ?line S3Ai = S1Ai,
    %%
    ?line H_d = socket_req(S2, recv),
    ?line ok = socket_call(S3, {send,S3Ai,Stream,<<"Data H_d">>}),
    ?line {Addr,P3,S2Ai,Stream,<<"Data H_d">>} = socket_resp(H_d),
    ?line ok = socket_call(S3, {send,Socket2,S2Ai,Stream,<<"Data S2">>}),
    ?line {Addr,P2,S3Ai,Stream,<<"Data S2">>} = socket_call(S2, {recv,Socket3}),
    %%
    ?line inet:i(sctp),
    ?line ok = socket_stop(S1),
    ?line ok = socket_stop(S2),
    ?line {Addr,P2,[],#sctp_shutdown_event{assoc_id=S1Ai}} =
	ok(socket_stop(S3)),
    ok.

%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light

socket_peeloff(Socket, AssocId, Timeout) ->
    Starter =
	fun () ->
		{ok,NewSocket} =
		    gen_sctp:peeloff(Socket, AssocId),
		NewSocket
	end,
    socket_starter(Starter, Timeout).

socket_start(Addr, Timeout) ->
    Starter =
	fun () ->
		{ok,Socket} =
		    gen_sctp:open([{type,seqpacket},{ifaddr,Addr}]),
		Socket
	end,
    socket_starter(Starter, Timeout).

socket_starter(Starter, Timeout) ->
    Parent = self(),
    Owner =
	spawn_link(
	  fun () ->
		  socket_starter(Starter(), Timeout, Parent)
	  end),
    io:format("Started socket ~w.~n", [Owner]),
    Owner.

socket_starter(Socket, Timeout, Parent) ->
    try
	Handler = socket_handler(Socket, Timeout),
	socket_loop(Socket, Timeout, Parent, Handler)
    catch
	Class:Reason ->
	    Stacktrace = erlang:get_stacktrace(),
	    io:format(?MODULE_STRING":socket exception ~w:~w at~n"
		      "~p.~n", [Class,Reason,Stacktrace]),
	    erlang:raise(Class, Reason, Stacktrace)
    end.

socket_loop(Socket, Timeout, Parent, Handler) ->
    receive
	{Parent,Ref} -> % socket_stop()
	    Result =
		case log(gen_sctp:recv(Socket, Timeout)) of
		    {error,timeout} -> ok;
		    R -> R
		end,
	    ok = gen_sctp:close(Socket),
	    Parent ! {self(),Ref, Result};
	{Parent,Ref,Msg} ->
	    Parent ! {self(),Ref,Handler(Msg)},
	    socket_loop(Socket, Timeout, Parent, Handler)
    end.

socket_handler(Socket, Timeout) ->
    fun ({listen,Listen}) ->
	    gen_sctp:listen(Socket, Listen);
	(port) ->
	    ok(inet:port(Socket));
	(socket) ->
	    Socket;
	(recv_assoc) ->
	    {AssocAddr,AssocPort,[],
	     #sctp_assoc_change{state=comm_up,
				error=0,
				outbound_streams=Os,
				inbound_streams=Is,
				assoc_id=AssocId}} =
		ok(gen_sctp:recv(Socket, infinity)),
	    case log(gen_sctp:recv(Socket, Timeout)) of
		{ok,AssocAddr,AssocPort,[],
		 #sctp_paddr_change{addr = {AssocAddr,AssocPort},
				    state = addr_available,
				    error = 0,
				    assoc_id = AssocId}} -> ok;
		{error,timeout} -> ok
	    end,
	    {AssocId,Os,Is,AssocAddr,AssocPort};
	({connect,ConAddr,ConPort,ConOpts}) ->
	    #sctp_assoc_change{state=comm_up,
			       error=0,
			       outbound_streams=Os,
			       inbound_streams=Is,
			       assoc_id=AssocId} =
		ok(gen_sctp:connect(Socket, ConAddr, ConPort, ConOpts)),
	    case log(gen_sctp:recv(Socket, Timeout)) of
		{ok,ConAddr,ConPort,[],
		 #sctp_paddr_change{addr = {ConAddr,ConPort},
				    state = addr_available,
				    error = 0,
				    assoc_id = AssocId}} -> ok;
		{error,timeout} -> ok
	    end,
	    {AssocId,Os,Is};
	({send,AssocId,Stream,Data}) ->
	    gen_sctp:send(Socket, AssocId, Stream, Data);
	({send,S,AssocId,Stream,Data}) ->
	    gen_sctp:send(S, AssocId, Stream, Data);
	(recv) ->
	    {Addr,Port,
	     [#sctp_sndrcvinfo{stream=Stream,assoc_id=AssocId}],Data} =
		ok(gen_sctp:recv(Socket, infinity)),
	    {Addr,Port,AssocId,Stream,Data};
	({recv,S}) ->
	    {Addr,Port,
	     [#sctp_sndrcvinfo{stream=Stream,assoc_id=AssocId}],Data} =
		ok(gen_sctp:recv(S, infinity)),
	    {Addr,Port,AssocId,Stream,Data}
    end.

socket_stop(Handler) ->
    Mref = erlang:monitor(process, Handler),
    Handler ! {self(),Mref},
    receive
	{Handler,Mref,Result} ->
	    receive {'DOWN',Mref,_,_,_} -> Result end;
	{'DOWN',Mref,_,_,Error} ->
	    exit(Error)
    end.

socket_call(Handler, Request) ->
    socket_resp(socket_req(Handler, Request)).

socket_req(Handler, Request) ->
    Mref = erlang:monitor(process, Handler),
    Handler ! {self(),Mref,Request},
    {Handler,Mref}.

socket_resp({Handler,Mref}) ->
    receive
	{'DOWN',Mref,_,_,Error} ->
	    exit(Error);
	{Handler,Mref,Reply} ->
	    erlang:demonitor(Mref),
	    receive {'DOWN',Mref,_,_,_} -> ok after 0 -> ok end,
	    Reply
    end.