anchor.diff:  %%                {117,98},
   %%                {149,125}]}
   %%
  -%% This turns out to have been due to SCTP resends as a consequence of
  -%% the listener having an insufficient recbuf. Increasing the size
  -%% solves the problem.
  -%%
  -
  -send_from_multiple_clients(N, Sz)
  -  when is_integer(N), 0 < N, is_integer(Sz), 0 < Sz ->
  -    timer:tc(fun listen/2, [N, <<?MAGIC, 0:Sz/unit:8>>]).
  -
  -%% listen/2
  -
  -listen(N, Bin) ->
  +
  +send_multiple(Clients, Msgs, Sz)
  +  when is_integer(Clients), 0 < Clients,
  +       is_integer(Msgs), 0 < Msgs,
  +       is_integer(Sz), 0 < Sz ->
  +    T0 = diameter_lib:now(),
  +    {S, Res} = timer:tc(fun listen/3, [Clients, Msgs, Sz]),
  +    report(T0, Res),
  +    Ts = lists:append(Res),
  +    Outgoing = [DT || {_,{_,_,DT},{_,_,_},_} <- Ts],
  +    Incoming = [DT || {_,{_,_,_},{_,_,DT},_} <- Ts],
  +    Diffs = [lists:max(L) - lists:min(L) || L <- [Outgoing, Incoming]],
  +    {S,
  +     S < ?FOREVER*1000,
  +     Diffs,
  +     [D < V || V <- [?VARIANCE*1000], D <- Diffs],
  +     [T || T <- Ts, [] == [T || {_,{_,_,_},{_,_,_},_} <- [T]]],
  +     Res}.
  +
  +%% listen/3
  +
  +listen(Clients, Msgs, Sz) ->
       {ok, Sock} = open(),
       ok = gen_sctp:listen(Sock, true),
       {ok, PortNr} = inet:port(Sock),
   
       %% Spawn a middleman that in turn spawns N connecting processes,
       %% collects a list of exit reasons and then exits with the list as
  -    %% reason. loop/3 returns when we receive this list from the
  +    %% reason. accept/2 returns when we receive this list from the
       %% middleman's 'DOWN'.
   
       Self = self(),
  -    Fun = fun() -> exit(connect2(Self, PortNr, Bin)) end,
  -    {_, MRef} = spawn_monitor(fun() -> exit(fold(N, Fun)) end),
  -    loop(Sock, MRef, Bin).
  -
  -%% fold/2
  +    Fun = fun() -> exit(client(Self, PortNr, Msgs, Sz)) end,  %% start clients
  +    {_, MRef} = spawn_monitor(fun() -> exit(clients(Clients, Fun)) end),
  +    accept_loop(Sock, MRef).
  +
  +%% fclients/2
   %%
   %% Spawn N processes and collect their exit reasons in a list.
   
  -fold(N, Fun) ->
  +clients(N, Fun) ->
       start(N, Fun),
       acc(N, []).
   
  +%% start/2
  +
   start(0, _) ->
       ok;
  +
   start(N, Fun) ->
       spawn_monitor(Fun),
       start(N-1, Fun).
   
  +%% acc/2
  +
   acc(0, Acc) ->
       Acc;
  +
   acc(N, Acc) ->
       receive
           {'DOWN', _MRef, process, _, RC} ->
               acc(N-1, [RC | Acc])
       end.
   
  -%% loop/3
  -
  -loop(Sock, MRef, Bin) ->
  +%% accept_loop/2
  +
  +accept_loop(Sock, MRef) ->
  +    ok = inet:setopts(Sock, [{active, once}]),
       receive
  -        ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B})
  +        ?SCTP(Sock, {_, #sctp_assoc_change{state = comm_up,
  +                                           outbound_streams = OS,
  +                                           assoc_id = Id}}) ->
  +            Self = self(),
  +            TPid = spawn(fun() -> assoc(monitor(process, Self), Id, OS) end),
  +            NewSock = peeloff(Sock, Id, TPid),
  +            TPid ! {peeloff, NewSock},
  +            accept_loop(Sock, MRef);
  +        ?SCTP(Sock, _) ->
  +            accept_loop(Sock, MRef);
  +        {'DOWN', MRef, process, _, Reason} ->
  +            Reason;
  +        T ->
  +            error(T)
  +    end.
  +
  +%% assoc/3
  +%%
  +%% Server process that answers incoming messages as long as the parent
  +%% lives.
  +
  +assoc(MRef, Id, OS)
  +  when is_reference(MRef) ->
  +    {peeloff, Sock} = receive T -> T end,
  +    recv_loop(Sock, Id, sender(Sock, Id, OS), MRef).
  +
  +%% recv_loop/4
  +
  +recv_loop(Sock, Id, Pid, MRef) ->
  +    ok = inet:setopts(Sock, [{active, once}]),
  +    receive
  +        ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = I}], B})
             when is_binary(B) ->
  -            Sz = size(Bin),
  -            {Sz, Bin} = {size(B), B},  %% assert
  -            ok = send(Sock, Id, mark(Bin)),
  -            loop(Sock, MRef, Bin);
  +            T2 = diameter_lib:now(),
  +            I = Id,                      %% assert
  +            <<?MAGIC, Bin/binary>> = B,  %% assert
  +            {[_,_,_,Sz] = L, Bytes} = unmark(Bin),
  +            Sz = size(Bin) - Bytes,      %% assert
  +            <<_:Bytes/binary, Body:Sz/binary>> = Bin,
  +            send(Pid, [T2|L], Body),  %% answer
  +            recv_loop(Sock, Id, Pid, MRef);
           ?SCTP(Sock, _) ->
  -            loop(Sock, MRef, Bin);
  +            recv_loop(Sock, Id, Pid, MRef);
           {'DOWN', MRef, process, _, Reason} ->
  -            Reason
  -    end.
  -
  -%% connect2/3
  -
  -connect2(Pid, PortNr, Bin) ->
  +            Reason;
  +        T ->
  +            error(T)
  +    end.
  +
  +%% send/3
  +
  +send(Pid, Header, Body) ->
  +    Pid ! {send, Header, Body}.
  +
  +%% sender/3
  +%%
  +%% Start a process that sends, so as not to block the controlling process.
  +
  +sender(Sock, Id, OS) ->
  +    Pid = self(),
  +    spawn(fun() -> send_loop(Sock, Id, OS, 1, monitor(process, Pid)) end).
  +
  +%% send_loop/5
  +
  +send_loop(Sock, Id, OS, N, MRef) ->
  +    receive
  +        {send, L, Body} ->
  +            Stream = N rem OS,
  +            ok = send(Sock, Id, Stream, mark(Body, [N, Stream | L])),
  +            send_loop(Sock, Id, OS, N+1, MRef);
  +        {'DOWN', MRef, process, _, _} = T ->
  +            T;
  +        T ->
  +            error(T)
  +    end.
  +
  +%% peeloff/3
  +
  +peeloff(LSock, Id, TPid) ->
  +    {ok, Sock} = gen_sctp:peeloff(LSock, Id),
  +    ok = gen_sctp:controlling_process(Sock, TPid),
  +    Sock.
  +
  +%% client/4
  +
  +client(Pid, PortNr, Msgs, Sz) ->
       monitor(process, Pid),
  -
       {ok, Sock} = open(),
       ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []),
  -    Id = assoc(Sock),
  -
  -    %% T1 = time before send
  -    %% T2 = time after listening process received our message
  -    %% T3 = time after reply is received
  -
  -    T1 = diameter_lib:now(),
  -    ok = send(Sock, Id, Bin),
  -    T2 = unmark(recv(Sock, Id)),
  -    T3 = diameter_lib:now(),
  -    {diameter_lib:micro_diff(T2, T1),  %% Outbound
  -     diameter_lib:micro_diff(T3, T2)}. %% Inbound
  -
  -%% recv/2
  -
  -recv(Sock, Id) ->
  -    receive
  -        ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = I}], Bin})
  -          when is_binary(Bin) ->
  -            Id = I,   %% assert
  -            Bin;
  -        ?SCTP(S, _) ->
  -            Sock = S, %% assert
  -            recv(Sock, Id);
  -        T ->
  -            exit(T)
  -    end.
  -
  -%% send/3
  -
  -send(Sock, Id, Bin) ->
  -    gen_sctp:send(Sock, Id, 0, Bin).
  -
  -%% mark/1
  -
  -mark(Bin) ->
  -    Info = term_to_binary(diameter_lib:now()),
  +    recv_loop(Sock, Msgs, Sz).
  +
  +%% recv_loop/3
  +
  +recv_loop(_, 0, T) ->
  +    [_,_|Acc] = T,
  +    Acc;
  +
  +recv_loop(Sock, Msgs, T) ->
  +    ok = inet:setopts(Sock, [{active, once}]),
  +    {I, NewT} = recv(Sock, Msgs, T, receive X -> X end),
  +    recv_loop(Sock, Msgs - I, NewT).
  +
  +%% recv/4
  +
  +recv(Sock, Msgs, Sz, ?SCTP(Sock, {_, #sctp_assoc_change{} = A})) ->
  +    #sctp_assoc_change{state = comm_up,  %% assert
  +                       assoc_id = Id,
  +                       outbound_streams = OS}
  +        = A,
  +    true = is_integer(Sz),  %% assert
  +    send_n(Msgs, sender(Sock, Id, OS), Sz),
  +    {0, [Id, OS]};
  +
  +recv(Sock, _, T, ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin})) ->
  +    T4 = diameter_lib:now(),
  +    [Id, OS | Acc] = T,
  +    {1, [Id, OS, stat(T4, Bin) | Acc]};
  +
  +recv(Sock, _, T, ?SCTP(Sock, _)) ->
  +    {0, [_,_|_] = T};
  +
  +recv(_, _, _, T) ->
  +    error(T).
  +
  +%% send_n/3
  +%%
  +%% Send messages to the server from dedicated processes.
  +
  +send_n(0, _, _) ->
  +    ok;
  +
  +send_n(N, Pid, Sz) ->
  +    M = rand:uniform(255),
  +    send(Pid, [Sz], binary:copy(<<M>>, Sz)),
  +    send_n(N-1, Pid, Sz).
  +
  +%% send/4
  +
  +send(Sock, Id, Stream, Bin) ->
  +    case gen_sctp:send(Sock, Id, Stream, <<?MAGIC, Bin/binary>>) of
  +        {error, eagain} ->
  +            send(Sock, Id, Stream, Bin);
  +        RC ->
  +            RC
  +    end.
  +
  +%% stat/2
  +
  +stat(T4, <<?MAGIC, Bin/binary>>) ->
  +    %% T1 = time at send
  +    %% T2 = time at reception by server
  +    %% T3 = time at reception by server's sender
  +    %% T4 = time at reception of answer
  +
  +    {[T3,NI,SI,T2,T1,NO,SO,Sz], Bytes} = unmark(Bin),
  +
  +    Sz = size(Bin) - Bytes,  %% assert
  +
  +    {T1,
  +     {NO, SO, diameter_lib:micro_diff(T2, T1)},  %% Outbound
  +     {NI, SI, diameter_lib:micro_diff(T4, T3)},  %% Inbound
  +     T4}.
  +
  +%% mark/2
  +
  +mark(Bin, T) ->
  +    Info = term_to_binary([diameter_lib:now() | T]),
       <<Info/binary, Bin/binary>>.
  -
  +    
   %% unmark/1
   
   unmark(Bin) ->
  -    binary_to_term(Bin).
  +    T = binary_to_term(Bin),
  +    {T, size(term_to_binary(T))}.
   
   %% ===========================================================================
   
  +%% send_many_from_one/0
  +%%
  +%% Demonstrates sluggish delivery of messages.
  +
  +send_many_from_one() ->
  +    [{timetrap, {seconds, 30}}].
  +
  +send_many_from_one(_) ->
  +    ?OK = send_multiple(1, 128, 1024).
  +
  +%% ===========================================================================
  +
   %% receive_what_was_sent/1
   %%
   %% Demonstrates reception of a message that differs from that sent.
   
   receive_what_was_sent(_Config) ->
  -    send_from_multiple_clients(1, 1024*32).  %% fails
  +    ?OK = send_multiple(1, 1, 1024*32).
   
   %% ===========================================================================
   
  
positive.code:     [{timetrap, {seconds, 10}}].
  
  all() ->
      [send_not_from_controlling_process,
       send_from_multiple_clients,
       receive_what_was_sent].
  
  init_per_suite(Config) ->
  
negative.code: %%               {213,159},
  %%               {373,173},
  %%               {376,118}]}
  %%     8> send_one_from_many(8, 1024).
  %%     {21001891,[{20999968,128},
  %%                {8997891,172},
  %%                {8997927,91},
  