anchor.diff: +%%%-------------------------------------------------------------------
  +%%% File    : transactionstatelist.erl
  +%%% Author  : Fredrik Thulin <ft@it.su.se>
  +%%% Descrip.: Transaction layer's list-module. A mix of code
  +%%%           manipulating a list of transactions, and code to locate
  +%%%           transactions in all strange ways the SIP RFC3261
  +%%%           prescribe.
  +%%%
  +%%% Created : 05 Feb 2004 by Fredrik Thulin <ft@it.su.se>
  +%%%-------------------------------------------------------------------
   -module(transactionstatelist).
  --export([add_client_transaction/4, add_server_transaction/3, 
  -	 delete_using_pid/2, delete_expired/1,
  -	 empty/0, extract/2, debugfriendly/1, get_client_transaction/3,
  -	 get_server_transaction_using_request/2, get_server_transaction_using_response/2,
  -	 get_elem_using_pid/2, get_list_using_pid/2,
  -	 set_pid/2, set_appdata/2, set_response_to_tag/2,
  -	 update_transactionstate/2, append_response_branch/3,
  +
  +%%--------------------------------------------------------------------
  +%% External exports - note that these functions should _only_be_used_
  +%% by the transaction layer modules. This module is INTERNAL to the
  +%% transaction layer.
  +%%--------------------------------------------------------------------
  +-export([
  +	 add_client_transaction/4,
  +	 add_server_transaction/3,
  +	 delete_using_entrylist/1,
  +	 delete_expired/0,
  +	 empty/0,
  +	 extract/2,
  +	 debugfriendly/0,
  +	 debugfriendly/1,
  +	 monitor_format/1,
  +	 get_client_transaction/2,
  +	 get_server_transaction_using_request/1,
  +	 get_server_transaction_using_response/1,
  +	 get_elem_using_pid/2,
  +	 get_entrylist_using_pid/1,
  +	 set_pid/2,
  +	 set_appdata/2,
  +	 set_response_to_tag/2,
  +	 set_result/2,
  +	 update_transactionstate/1,
  +	 append_response_branch/3,
   	 get_server_transaction_using_stateless_response_branch/3,
  -	 get_length/1]).
  -
  +	 get_length/0,
  +	 get_all_entries/0
  +	]).
  +
  +%%--------------------------------------------------------------------
  +%% Include files
  +%%--------------------------------------------------------------------
   -include("transactionstatelist.hrl").
   -include("siprecords.hrl").
   
  -%%-record(transactionstate, {ref, id, ack_id, branch, pid, appdata,
  -%%	request, response_to_tag, expire}).
  -
  -add(Type, Id, AckId, Pid, TStateList) when record(TStateList, transactionstatelist), pid(Pid) ->
  -    case get_elem(Type, Id, TStateList) of
  -	none ->
  -	    NewT = #transactionstate{ref=make_ref(), type=Type, id=Id, ack_id=AckId, pid=Pid,
  -	    			     stateless_response_branches=[]},
  -	    #transactionstatelist{list=lists:append(TStateList#transactionstatelist.list, [NewT])};
  -	_ ->
  -	    logger:log(error, "transactionstatelist: Asked to add transaction with duplicate Id ~p to list :~n~p",
  -	    	       [Id, debugfriendly(TStateList)]),
  -	    TStateList
  -    end.
  -
  -
  -add_client_transaction(Method, Branch, Pid, TStateList) when record(TStateList, transactionstatelist), pid(Pid) ->
  +%%--------------------------------------------------------------------
  +%% Records
  +%%--------------------------------------------------------------------
  +-record(tables, {ref_to_t, pid_to_ref, ack_to_ref, typeid_to_ref}).
  +
  +%%--------------------------------------------------------------------
  +%% Macros
  +%%--------------------------------------------------------------------
  +
  +%%====================================================================
  +%% External functions
  +%%====================================================================
  +
  +%%--------------------------------------------------------------------
  +%% Function: add_client_transaction(Method, Branch, Pid, Desc)
  +%%           Method     = string()
  +%%           Branch     = string()
  +%%           Pid        = pid()
  +%%           Desc       = string(), description of transaction
  +%% Descrip.: Add a new client transaction state entry to TStateList.
  +%% Returns : ok               |
  +%%           error            |
  +%%           {duplicate, Dup}
  +%%           Dup = transactionstate record()
  +%%--------------------------------------------------------------------
  +add_client_transaction(Method, Branch, Pid, Desc)
  +  when is_list(Method), is_list(Branch), is_pid(Pid), is_list(Desc) ->
       Id = {Branch, Method},
  -    add(client, Id, none, Pid, TStateList).
  -
  -add_server_transaction(Request, Pid, TStateList) when record(Request, request), record(TStateList, transactionstatelist), pid(Pid) ->
  +    add(client, Id, none, Pid, Desc).
  +
  +%%--------------------------------------------------------------------
  +%% Function: add_server_transaction(Request, Pid, Desc)
  +%%           Request    = request record()
  +%%           Pid        = pid()
  +%%           Desc       = string(), description of transaction
  +%% Descrip.: Add a new transaction state entry to SocketList.
  +%% Returns : ok               |
  +%%           error            |
  +%%           {duplicate, Dup}
  +%%           Dup = transactionstate record()
  +%%--------------------------------------------------------------------
  +add_server_transaction(Request, Pid, Desc)
  +  when is_record(Request, request), is_pid(Pid), is_list(Desc) ->
       case sipheader:get_server_transaction_id(Request) of
   	error ->
   	    logger:log(error, "transactionstatelist: Could not get server transaction id for request"),
  -	    TStateList;
  +	    error;
   	Id ->
   	    AckId = case Request#request.method of
   		        "INVITE" ->
  
positive.code: 		       [Id, debugfriendly(TState)]),
  	    TState;
  	_ ->
  	    TState#transactionstate{stateless_response_branches=lists:append(In, [Id])}
      end.
  
  delete_expired(TStateList) when record(TStateList, transactionstatelist) ->
      Now = util:timestamp(),
      #transactionstatelist{list=del_time(Now, TStateList#transactionstatelist.list)}.
  
  delete_using_pid(TState, TStateList) when record(TStateList, transactionstatelist) ->
      #transactionstatelist{list=del_pid(TState, TStateList#transactionstatelist.list)}.
  
  
  
  del_pid(Pid, []) ->
      [];
  del_pid(Pid, [H | T]) when record(H, transactionstate), H#transactionstate.pid == Pid ->
      del_pid(Pid, T);
  del_pid(Pid, [H | T]) when record(H, transactionstate) ->
      [H | del_pid(Pid, T)].
  
  del_time(Time, []) ->
      [];
  del_time(Time, [H | T]) when record(H, transactionstate), H#transactionstate.expire =< Time, H#transactionstate.expire > 0 ->
      case util:safe_is_process_alive(H#transactionstate.pid) of
  	{true, Pid} ->
  	    %% Be nice and tell lingering processes it is time to go
  	    logger:log(error, "Transaction layer: Had to tell lingering transaction that it is time to terminate :~n~p",
  		       [debugfriendly(H)]),
  	    gen_server:cast(Pid, {expired});
  	_ ->
  	    true
      end,
      del_time(Time, T);
  del_time(Time, [H | T]) when record(H, transactionstate) ->
      [H | del_time(Time, T)].
  
  
  
  update_transactionstate(TState, TStateList) when record(TState, transactionstate), record(TStateList, transactionstatelist) ->
      Ref = TState#transactionstate.ref,
      #transactionstatelist{list=update_transactionstate(Ref, TState, TStateList#transactionstatelist.list, TStateList)}.
  
  update_transactionstate(Ref, _, [], TStateList) ->
      logger:log(error, "Transaction state list: Asked to update a transactionstate with ref=~p, but I can't find it", [Ref]),
      logger:log(debug, "Transaction state list: Asked to update a transactionstate with ref=~p, but I can't find it in list :~n~p",
  	       [Ref, debugfriendly(TStateList)]),
      [];
  update_transactionstate(Ref, NewT, [H | T], TStateList) when record(H, transactionstate), H#transactionstate.ref == Ref ->
      [NewT | T];
  update_transactionstate(Ref, NewT, [H | T], TStateList) when record(H, transactionstate) ->
      lists:append([H], update_transactionstate(Ref, NewT, T, TStateList)).
  
  %% Function: get_length/1
  %% Description: Returns the length of the list record element of a
  %%              transactionstatelist.
  %% Returns: Length
  %%--------------------------------------------------------------------
  get_length(T) when record(T, transactionstatelist) ->
      length(T#transactionstatelist.list).
  
  %% Function: debugfriendly/1
  %% Description: Returns a list, suitable for debug logging (using ~p),
  %%              describing the contents of a transactionstatelist.
  %% Returns: Blob
  %%          []
  %%--------------------------------------------------------------------
  debugfriendly([]) ->
      [];
  debugfriendly(TStateList) when record(TStateList, transactionstatelist) ->
      debugfriendly2(TStateList#transactionstatelist.list, []);
  debugfriendly(TState) when record(TState, transactionstate) ->
      debugfriendly2([TState], []).
  
  debugfriendly2([], Res) ->
      lists:reverse(Res);
  debugfriendly2([H | T], Res) when record(H, transactionstate) ->
      In = [{id, H#transactionstate.id}, {type, H#transactionstate.type}, {ref, H#transactionstate.ref}],
      Out = debugfriendly_non_empty([pid, ack_id, response_to_tag, appdata, branches], H, In),
      debugfriendly2(T, [Out | Res]).
  
  debugfriendly_non_empty([], R, Res) ->
      lists:reverse(Res);
  %% pid, not empty
  debugfriendly_non_empty([pid | T], R, Res) when R#transactionstate.pid /= undefined, R#transactionstate.pid /= none ->
      debugfriendly_non_empty(T, R, [{pid, R#transactionstate.pid} | Res]);
  %% response_to_tag, not empty
  debugfriendly_non_empty([response_to_tag | T], R, Res) when R#transactionstate.response_to_tag /= undefined, R#transactionstate.response_to_tag /= none ->
      debugfriendly_non_empty(T, R, [{response_to_tag, R#transactionstate.response_to_tag} | Res]);
  %% ack_id, not empty
  debugfriendly_non_empty([ack_id | T], R, Res) when R#transactionstate.ack_id /= undefined, R#transactionstate.ack_id /= none ->
      debugfriendly_non_empty(T, R, [{ack_id, R#transactionstate.ack_id} | Res]);
  %% appdata, not empty
  debugfriendly_non_empty([appdata | T], R, Res) when R#transactionstate.appdata /= undefined, R#transactionstate.appdata /= none ->
      debugfriendly_non_empty(T, R, [{appdata, R#transactionstate.appdata} | Res]);
  %% branches (length), not zero
  debugfriendly_non_empty([branches | T], R, Res) when length(R#transactionstate.stateless_response_branches) /= 0 ->
      debugfriendly_non_empty(T, R, [{stateless_response_branches, length(R#transactionstate.stateless_response_branches)} | Res]);
  %% empty, or unknown record field
  debugfriendly_non_empty([_ | T], R, Res) ->
      debugfriendly_non_empty(T, R, Res).
  
negative.code: 		    %%
  		    %% Bug in RFC3261, see http://bugs.sipit.net/sipwg/show_bug.cgi?id=755
  		    %%
  		    logger:log(debug, "Transaction state list: Found no matching server "
  			       "transaction for ACK using RFC3261 methods, trying RFC2543 too"),
  		    get_server_transaction_ack_2543(Request);
  		T ->
  		    T
  	    end
      end.
  
  %%--------------------------------------------------------------------
  %% Function: get_server_transaction_ack_2543(Request)
  %%           Request    = request record()
  %% Descrip.: Given an (newly received) ACK that we have already
  %%           determined has no RFC3261 Via branch parameter, try to
  %%           locate an already existing server transaction handler
  %%           using the painful old RFC2543 backwards compatible code.
  %% Returns : Entry |
  %%           error |
  %%           none
  %%           Entry = transactionstate record()
  %%--------------------------------------------------------------------
  get_server_transaction_ack_2543(Request) when is_record(Request, request) ->
      %% ACK requests are matched to transactions differently if they are not received from
      %% an RFC3261 compliant device, see RFC3261 17.2.3
      case sipheader:get_server_transaction_ack_id_2543(Request) of
  	error ->
  	    logger:log(error, "Transaction state list: Could not get server transaction RFC2543 ACK id for request"),
  	    error;
  	Id ->
  	    ToTag = sipheader:get_tag(keylist:fetch("To", Request#request.header)),
  	    case get_elem_ackid(Id, ToTag) of
  		none ->
  		    logger:log(debug, "Transaction state list: ACK request does not match any existing transaction"),
  		    %% If this ever happens, this extra debug output will probably be crucial to
  		    %% diagnose why.
  		    %%logger:log(debug, "Transaction state list: Extra debug: Looked for server transaction with id ~p "
  			%%       "AND to-tag ~p in list :~n~p", [Id, ToTag, debugfriendly()]),
  		    none;
  		Res when is_record(Res, transactionstate) ->
  		    Res
  	    end
      end.
  
  %%--------------------------------------------------------------------
  %% Function: get_server_transaction_using_response(Response)
  %%           Response   = response record()
  %% Descrip.: Look for a server transaction given a (newly received)
  %%           response.
  %% Returns : Entry |
  %%           none
  %%           Entry = transactionstate record()
  %%--------------------------------------------------------------------
  get_server_transaction_using_response(Response) when is_record(Response, response) ->
      case sipheader:get_client_transaction_id(Response) of
  	error ->
  	    none;
  	Id ->
  	    get_elem(server, Id)
      end.
  
  %%--------------------------------------------------------------------
  %% Function: get_client_transaction(Method, Branch)
  %%           Method     = string()
  %%           Branch     = string()
  %% Descrip.: Look for a client transaction using a method and branch.
  %% Returns : Entry |
  %%           none
  %%           Entry = transactionstate record()
  %% Notes   : This code is only used in transcationlayer before adding
  %%           a new client transaction. Since we have only a single
  %%           transaction_layer process per node, this is not a race,
  %%           but it would be better to just look for duplicates in
  %%           add_client_transaction().
  %%--------------------------------------------------------------------
  get_client_transaction(Method, Branch) ->
      Id = {Branch, Method},
      get_elem(client, Id).
  
  %%--------------------------------------------------------------------
  %% Function: get_entrylist_using_pid(Pid)
  %%           Pid        = pid()
  %%           TStateList = transactionstatelist record()
  %% Descrip.: Find all elements in a transactionstatelist who have a
  %%           matching pid. Return a plain list of those. Use with
  %%           care.
  %% Returns : EntryList |
  %%           none
  %%           EntryList = list() of transactionstate record()
  %%--------------------------------------------------------------------
  get_entrylist_using_pid(Pid) when is_pid(Pid) ->
      case get_using_pid2(Pid) of
  	[] ->
  	    none;
  	L when is_list(L) ->
  	    L
      end.
  
  %%--------------------------------------------------------------------
  %% Function: get_elem_using_pid(Pid, TStateList)
  %%           Pid        = pid()
  %%           TStateList = transactionstatelist record()
  %% Descrip.: The same as get_list_using_pid/2 except this function
  %%           returns {error, Reason} if more than one record has
  %%           a matching pid.
  %% Returns : TransactionState |
  %%           {error, Reason}
  %%           none
  %%           TransactionState = transactionstate record()
  %%           Reason           = string()
  %%--------------------------------------------------------------------
  get_elem_using_pid(Pid, TStateList) when is_pid(Pid), is_record(TStateList, transactionstatelist) ->
      %%case get_using_pid2(Pid, TStateList#transactionstatelist.list, []) of
      case get_using_pid2(Pid) of
  	[] ->
  	    none;
  	[Elem] when is_record(Elem, transactionstate) ->
  	    Elem;
  	L when is_list(L) ->
  	    {error, "more than one transactionstate found"};
  	_ ->
  	    {error, "unknown result from get_using_pid2"}
      end.
  
  %%--------------------------------------------------------------------
  %% Function: get_using_pid2(Pid)
  %%           Pid    = pid()
  %% Descrip.: Internal function that returns a plain list (not a
  %%           transactionstatelist) of all the elements of a given
  %%           transactionstatelist who have a matching pid.
  %% Returns : Entrys |
  %%           []
  %%           Entrys = list() of transactionstate record()
  %%--------------------------------------------------------------------
  get_using_pid2(Pid) ->
      RefList = ets:lookup(transactionstate_pid_to_ref, Pid),
      fetch_using_ref_tuples(RefList).
  
  %%--------------------------------------------------------------------
  %% Function: get_server_transaction_using_stateless_response_branch
  %%                   (Branch, Method, TStateList)
  %%           Branch     = string()
  %%           Method     = string()
  %%           TStateList = transactionstatelist record()
  %% Descrip.: Look for a server transaction that has a statelessly
  %%           generated branch id in it's list of such id's. When
  %%           running stateful, no transactions has such id's.
  %% Returns : Entry |
  %%           none
  %%           Entry = transactionstate record()
  %% Notes   : This code is probably very slow, but it should be removed
  %%           rather than improved. It is only used if we are running
  %%           in stateless mode - and no current Yxa applications are.
  %%           Stateless SIP is the root of all evil. Make sure you
  %%           don't have to use this code.
  %%--------------------------------------------------------------------
  get_server_transaction_using_stateless_response_branch(_Branch, _Method, TStateList)
    when is_record(TStateList, transactionstatelist) ->
      {error, "NOT IMPLEMENTED"}.
  
  %%--------------------------------------------------------------------
  %% Function: get_elem(Type, Id)
  %%           Type       = atom() (client | server)
  %%           Id         = term()
  %% Descrip.: Find a single element from a transactionstatelist which
  %%           has a matching type and id.
  %% Returns : Entry |
  %%           none
  %%           Entry = transactionstate record()
  %%--------------------------------------------------------------------
  get_elem(Type, Id) when is_atom(Type); Type == server; Type == client ->
      TId = {Type, Id},
      RefList = ets:lookup(transactionstate_typeid_to_ref, TId),
      case fetch_using_ref_tuples(RefList) of
  	[] -> none;
  	[IdMatch] ->
  	    IdMatch
      end.
  
  
  %%--------------------------------------------------------------------
  %% Function: get_elem_ackid(AckId, ToTag)
  %%           AckId      = term()
  %%           ToTag      = string() | none
  %% Descrip.: Find a single element from a transactionstatelist which
  %%           has a matching ack_id and response_to_tag. Only look at
  %%           server transactions (type == server). This is used when
  %%           matching RFC2543 ACK's to non-2xx responses to INVITEs.
  %% Returns : Entry |
  %%           none
  %%           Entry = transactionstate record()
  %%--------------------------------------------------------------------
  get_elem_ackid(AckId, ToTag) ->
      RefList = ets:lookup(transactionstate_ack_to_ref, AckId),
      AckIdMatches = fetch_using_ref_tuples(RefList),
      filter_server_totag_matches(ToTag, AckIdMatches).
  
  filter_server_totag_matches(_ToTag, []) ->
      none;
  filter_server_totag_matches(ToTag, [#transactionstate{type=server, response_to_tag=ToTag}=H | _T]) ->
      %% We have a match
      H;
  filter_server_totag_matches(ToTag, [#transactionstate{type=server}=H | T]) ->
      logger:log(debug, "Transaction state list: Found a transaction with matching ACK-Id, "
  	       "but the to-tag is not ~p (it is ~p) :~n~p",
  	       [ToTag, H#transactionstate.response_to_tag, debugfriendly(H)]),
      filter_server_totag_matches(ToTag, T);
  filter_server_totag_matches(ToTag, [H|T]) when is_record(H, transactionstate) ->
      filter_server_totag_matches(ToTag, T).
  
  %%--------------------------------------------------------------------
  %% Function: extract(Fields, TState)
  %%           Fields = list() of atom(), pid|appdata|response_to_tag
  %%           TState = transactionstate record()
  %% Descrip.: Return one or more values from a transactionstate record.
  %% Returns : Values = list()
  %%--------------------------------------------------------------------
  extract(Values, TState) when is_record(TState, transactionstate) ->
      extract(Values, TState, []).
  
  extract([], TState, Res) when is_record(TState, transactionstate) ->
      Res;
  extract([pid | T], TState, Res) when is_record(TState, transactionstate) ->
      extract(T, TState, lists:append(Res, [TState#transactionstate.pid]));
  extract([appdata | T], TState, Res) when is_record(TState, transactionstate) ->
      extract(T, TState, lists:append(Res, [TState#transactionstate.appdata]));
  extract([response_to_tag | T], TState, Res) when is_record(TState, transactionstate) ->
      extract(T, TState, lists:append(Res, [TState#transactionstate.response_to_tag])).
  
  %%--------------------------------------------------------------------
  %% Function: set_pid(TState, Value)
  %%           TState = transactionstate record()
  %%           Value  = pid()
  %% Descrip.: Set pid in a transactionstate record()
  %% Returns : NewTState = transactionstate record()
  %%--------------------------------------------------------------------
  set_pid(TState, Value) when is_record(TState, transactionstate), is_pid(Value); Value == none ->
      TState#transactionstate{pid = Value}.
  
  %%--------------------------------------------------------------------
  %% Function: set_appdata(TState, Value)
  %%           TState = transactionstate record()
  %%           Value  = term()
  %% Descrip.: Set appdata in a transactionstate record()
  %% Returns : NewTState = transactionstate record()
  %%--------------------------------------------------------------------
  set_appdata(TState, Value) when is_record(TState, transactionstate) ->
      TState#transactionstate{appdata = Value}.
  
  %%--------------------------------------------------------------------
  %% Function: set_response_to_tag(TState, Value)
  %%           TState = transactionstate record()
  %%           Value  = string() ???
  %% Descrip.: Set response_to_tag in a transactionstate record()
  %% Returns : NewTState = transactionstate record()
  %%--------------------------------------------------------------------
  set_response_to_tag(TState, Value) when is_record(TState, transactionstate) ->
      TState#transactionstate{response_to_tag = Value}.
  
  %%--------------------------------------------------------------------
  %% Function: set_result(TState, Value)
  %%           TState = transactionstate record()
  %%           Value  = string()
  %% Descrip.: Set result in a transactionstate record()
  %% Returns : NewTState = transactionstate record()
  %%--------------------------------------------------------------------
  set_result(TState, Value) when is_record(TState, transactionstate), is_list(Value) ->
      TState#transactionstate{result = Value}.
  
  %%--------------------------------------------------------------------
  %% Function: append_response_branch(TState, Branch, Method)
  %%           TState = transactionstate record()
  %%           Branch = string()
  %%           Method = string()
  %% Descrip.: Append an entry to this transcationstate's list of
  %%           stateless response branches, unless the very same entry
  %%           is already there. This is only used when running as a
  %%           stateless SIP stack.
  %% Returns : NewTState = transactionstate record()
  %% Notes   : Stateless SIP is the root of all evil. Make sure you
  %%           don't have to use this code.
  %%--------------------------------------------------------------------
  append_response_branch(TState, Branch, Method) when is_record(TState, transactionstate) ->
      In = TState#transactionstate.stateless_response_branches,
      Id = {Branch, Method},
      case lists:member(Id, In) of
  