anchor.diff:                                   F_ <- [fun(T__) -> T__ end,
                                           fun(T__) -> ?WD(T__) end]]]).
   
  -%% Transport types.
  --define(TRANSPORTS, [connect, accept]).
  -
  -%% Message over the transport interface.
  --define(TMSG(T), {diameter, T}).
  -
  -%% Receive a message within a specified time.
  --define(RECV(T, Timeout),
  -        receive T -> now()
  -        after Timeout -> ?ERROR({timeout, Timeout})
  +%% Watchdog timer of the misbehaving peer.
  +-define(PEER_WD, 10000).
  +
  +%% Receive a watchdog event within a specified time.
  +-define(EVENT(T, Tmo),
  +        receive #diameter_event{info = T} -> now()
  +        after Tmo -> ?ERROR({timeout, Tmo})
           end).
   
  -%% Receive a message in a given number of watchdogs, plus or minus
  +%% Receive an event in a given number of watchdogs, plus or minus
   %% half. Note that the call to now_diff assumes left to right
   %% evaluation order.
  --define(RECV(T, N, WdL, WdH),
  +-define(EVENT(T, N, WdL, WdH),
           [?ERROR({received, _Elapsed_, _LowerBound_, N, WdL})
            || _UpperBound_ <- [(N)*(WdH) + (WdH) div 2],
  -            _Elapsed_    <- [now_diff(now(), ?RECV(T, _UpperBound_))],
  +            _Elapsed_    <- [now_diff(now(), ?EVENT(T, _UpperBound_))],
               _LowerBound_ <- [(N)*(WdL) - (WdL) div 2],
               _Elapsed_ =< _LowerBound_*1000]).
  +
  +-define(EVENT(T, N, Wd),
  +        ?EVENT(T, N, Wd, Wd)).
   
   %% A timeout that ensures one watchdog. The ensure only one watchdog
   %% requires (Wd + 2000) + 1000 < 2*(Wd - 2000) ==> 7000 < Wd for the
  
positive.code: %%
  %% %CopyrightBegin%
  %%
  %% Copyright Ericsson AB 2010-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
  
negative.code:     %%                 NumDWA == 2          NumDWA++
      %%                                      Failback()           OKAY
      %%
      %%   REOPEN        Timer expires &      SendWatchdog()
      %%                 !Pending             SetWatchdog()
      %%                                      Pending = TRUE       REOPEN
      %%
      %% An exchange of 3 watchdogs (the first directly after
      %% capabilities exchange) brings the connection back up.
  
      ?EVENT({watchdog, Ref, _, {reopen, okay}, _}, 2, WdL, WdH),
      ?EVENT({up, Ref, _, _, #diameter_packet{}}, 0),
  
      %% Non-response brings it down again.
  
      ?EVENT({watchdog, Ref, _, {okay, suspect}, _}, 2, WdL, WdH),
      ?EVENT({down, Ref, _, _}, 0),
      ?EVENT({watchdog, Ref, _, {suspect, down}, _}, 1, WdL, WdH),
  
      %% Reestablish after another watchdog.
  
      recv_reopen(Type, Ref, WdL, WdH),
  
      %%   REOPEN        Timer expires &      NumDWA = -1
      %%                 Pending &            SetWatchdog()
      %%                 NumDWA >= 0                               REOPEN
      %%
      %%   REOPEN        Timer expires &      CloseConnection()
      %%                 Pending &            SetWatchdog()
      %%                 NumDWA < 0                                DOWN
      %%
      %% Peer is now ignoring all watchdogs go down again after 2
      %% timeouts.
  
      ?EVENT({watchdog, Ref, _, {reopen, down}, _}, 2, WdL, WdH);
  
  %% The misbehaving peer.
  recv(_, Type, SvcName, Ref, Wd, N, M) ->
      %% First transport process.
      ?EVENT({watchdog, Ref, _, {initial, okay}, _}, 1000),
      ?EVENT({up, Ref, _, _, #diameter_packet{}}, 0),
      reg(Type, Ref, SvcName, {SvcName, {Wd,N,M}}),
      ?EVENT({watchdog, Ref, _, {okay, down}, _}, infinity),
  
      %% Second transport process.
      ?EVENT({watchdog, Ref, _, {_, reopen}, _}, infinity),
      reg(Type, Ref, SvcName, 3),
      ?EVENT({watchdog, Ref, _, {_, down}, _}, infinity),
  
      %% Third transport process.
      ?EVENT({watchdog, Ref, _, {_, reopen}, _}, infinity),
      reg(Type, Ref, SvcName, 0),
      ?EVENT({watchdog, Ref, _, {_, down}, _}, infinity),
  
      ok.
  
  %% recv_reopen/4
  
  recv_reopen(connect, Ref, WdL, WdH) ->
      ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, WdL, WdH),
      ?EVENT({reconnect, Ref, _}, 0);
  
  recv_reopen(listen, Ref, _, _) ->
      ?EVENT({watchdog, Ref, _, {_, reopen}, _}, 1, ?PEER_WD).
  
  %% reg/4
  %%
  %% Lookup the pid of the transport process and publish a term for
  %% send/2 to lookup.
  reg(Type, Ref, SvcName, T) ->
      TPid = tpid(Type, Ref, diameter:service_info(SvcName, transport)),
      true = diameter_reg:add_new({?MODULE, TPid, T}).
      
  %% tpid/3
  
  tpid(connect, Ref, [[{ref, Ref},
                       {type, connect},
                       {options, _},
                       {watchdog, _},
                       {peer, _},
                       {apps, _},
                       {caps, _},
                       {port, [{owner, TPid} | _]}
                       | _]]) ->
      TPid;
  
  tpid(listen, Ref, [[{ref, Ref},
                      {type, listen},
                      {options, _},
                      {accept, As}
                      | _]]) ->
      [[{watchdog, _},
        {peer, _},
        {apps, _},
        {caps, _},
        {port, [{owner, TPid} | _]}
        | _]]
          = lists:filter(fun([{watchdog, {_,_,S}} | _]) ->
                                 S == okay orelse S == reopen 
                         end,
                         As),
      TPid.
  
  %% ===========================================================================
  
  listen(PortNr, Opts) ->
      gen_tcp:listen(PortNr, Opts).
  
  accept(LSock) ->
      gen_tcp:accept(LSock).
  
  connect(Addr, Port, Opts) ->
      gen_tcp:connect(Addr, Port, Opts).
  
  setopts(Sock, Opts) ->
      inet:setopts(Sock, Opts).
  
  send(Sock, Bin) ->
      send(getr(config), Sock, Bin).
  
  %% send/3
  
  %% First outgoing message from a new transport process is CER/CEA.
  %% Remaining outgoing messages are either DWR or DWA.
  send(undefined, Sock, Bin) ->
      putr(config, init),
      gen_tcp:send(Sock, Bin);
  
  %% Outgoing DWR: fake reception of DWA. Use the fact that AVP values
  %% are ignored. This is to ensure that the peer's watchdog state
  %% transitions are only induced by responses to messages it sends.
  send(_, Sock, <<_:32, 1:1, _:7, 280:24, _:32, EId:32, HId:32, _/binary>>) ->
      Pkt = #diameter_packet{header = #diameter_header{version = 1,
                                                       end_to_end_id = EId,
                                                       hop_by_hop_id = HId},
                             msg = ['DWA', {'Result-Code', 2001},
                                           {'Origin-Host', "XXX"},
                                           {'Origin-Realm', ?REALM}]},
      #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Pkt),
      self() ! {tcp, Sock, Bin},
      ok;
  
  %% First outgoing DWA.
  send(init, Sock, Bin) ->
      [{{?MODULE, _, T}, _}] = diameter_reg:wait({?MODULE, self(), '_'}),
      putr(config, T),
      send(Sock, Bin);
  
  %% First transport process.
  send({SvcName, {_,_,_} = T}, Sock, Bin) ->
      [{'Origin-Host', _} = OH, {'Origin-Realm', _} = OR | _]
          = ?SERVICE(SvcName),
      putr(origin, [OH, OR]),
      putr(config, T),
      send(Sock, Bin);
      
  %% Discard DWA, failback after another timeout in the peer.
  send({Wd, 0 = No, Msg}, Sock, Bin) ->
      Origin = getr(origin),
      spawn(fun() -> failback(?ONE_WD(Wd), Msg, Sock, Bin, Origin) end),
      putr(config, No),
      ok;
  
  %% Send DWA while we're in the mood (aka 0 < N).
  send({Wd, N, Msg}, Sock, Bin) ->
      putr(config, {Wd, N-1, Msg}),
      gen_tcp:send(Sock, Bin);
  
  %% Discard DWA.
  send(0, _Sock, _Bin) ->
      ok;
  
  %% Send DWA.
  send(N, Sock, <<_:32, 0:1, _:7, 280:24, _/binary>> = Bin) ->
      putr(config, N-1),
      gen_tcp:send(Sock, Bin).
  
  failback(Tmo, Msg, Sock, Bin, Origin) ->
      timer:sleep(Tmo),
      ok = gen_tcp:send(Sock, msg(Msg, Bin, Origin)).
  
  %% msg/2
  
  msg('DWA', Bin, _Origin) ->
      Bin;
  msg(Msg, _Bin, Origin) ->
      #diameter_packet{bin = Bin}
          = diameter_codec:encode(?BASE, msg(Msg, Origin)),
      Bin.
  
  msg('DWR' = M, T) ->
      [M | T];
  
  msg('RAA', T) ->
      ['RAA', {'Session-Id', diameter:session_id("abc")},
              {'Result-Code', 2001}
            | T].
  %% An unexpected answer is discarded after passing through the
  %% watchdog state machine.
  
  %% ===========================================================================
  
  peer_up(_SvcName, _Peer, S) ->
      S.
  
  peer_down(_SvcName, _Peer, S) ->
      S.
  
  %% ===========================================================================
  
  choose(true, X, _)  -> X;
  choose(false, _, X) -> X.
  
  %% id/1
  %%
  %% Jitter callback.
  
  id(T) ->
      T.
  
  %% run/1
  %%
  %% A more useful badmatch in case of failure.
  
  run(Fs) ->
      ?util:run([{?MODULE, [run1, F]} || F <- Fs]).
  
  run1([F|A]) ->
      ok = try
               apply(?MODULE, F, A),
               ok
           catch
               E:R ->
                   S = erlang:get_stacktrace(),
                   io:format("~p~n", [{A, E, R, S}]),
                   S
           end.
  
  %% now_diff/2
  