Current section

Files

Jump to
esip src esip_transaction.erl
Raw

src/esip_transaction.erl

%%%----------------------------------------------------------------------
%%% File : esip_transaction.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose : Route client/server transactions
%%% Created : 15 Jul 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2017 ProcessOne, SARL. All Rights Reserved.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%%----------------------------------------------------------------------
-module(esip_transaction).
-behaviour(gen_server).
%% API
-export([start_link/0, process/2, reply/2, cancel/2,
request/3, request/4, insert/4, delete/3, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("esip.hrl").
-include("esip_lib.hrl").
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
process(SIPSock, #sip{method = Method, hdrs = Hdrs, type = request} = Req) ->
Branch = esip:get_branch(Hdrs),
case lookup(transaction_key(Branch, Method, server)) of
{ok, Pid} ->
esip_server_transaction:route(Pid, Req);
error when Method == <<"ACK">> ->
case esip_dialog:lookup(esip_dialog:id(uas, Req)) of
{ok, Core, _} ->
pass_to_core(Core, Req, SIPSock);
_Err ->
esip:callback(request, [Req, SIPSock])
end;
error when Method == <<"CANCEL">> ->
case lookup({esip:to_lower(Branch), server}) of
{ok, Pid} ->
esip_server_transaction:route(Pid, Req),
start_server_transaction(SIPSock, Req);
error ->
case esip:callback(request, [Req, SIPSock]) of
#sip{type = response} = Resp ->
esip_transport:send(SIPSock, Resp);
drop ->
ok;
_ ->
Resp = esip:make_response(
Req,
#sip{type = response, status = 481},
esip:make_tag()),
esip_transport:send(SIPSock, Resp)
end
end;
error ->
start_server_transaction(SIPSock, Req)
end;
process(SIPSock, #sip{method = Method, hdrs = Hdrs, type = response} = Resp) ->
Branch = esip:get_branch(Hdrs),
case lookup(transaction_key(Branch, Method, client)) of
{ok, Pid} ->
esip_client_transaction:route(Pid, Resp);
_ ->
esip:callback(response, [Resp, SIPSock])
end.
reply(#trid{owner = Pid, type = server}, #sip{type = response} = Resp) ->
esip_server_transaction:route(Pid, Resp);
reply(#sip{method = Method, type = request, hdrs = Hdrs},
#sip{type = response} = Resp) ->
Branch = esip:get_branch(Hdrs),
case lookup(transaction_key(Branch, Method, server)) of
{ok, Pid} ->
esip_server_transaction:route(Pid, Resp);
error ->
ok
end;
reply(_, _) ->
ok.
request(SIPSock, Req, TU) ->
request(SIPSock, Req, TU, []).
request(SIPSock, #sip{type = request} = Req, TU, Opts) ->
start_client_transaction(SIPSock, Req, TU, Opts).
cancel(#sip{method = Method, type = request, hdrs = Hdrs}, TU) ->
Branch = esip:get_branch(Hdrs),
case lookup(transaction_key(Branch, Method, client)) of
{ok, Pid} ->
esip_client_transaction:cancel(Pid, TU);
error ->
ok
end;
cancel(#trid{type = client, owner = Pid}, TU) ->
esip_client_transaction:cancel(Pid, TU).
stop(#trid{owner = Pid, type = client}) ->
esip_client_transaction:stop(Pid);
stop(#trid{owner = Pid, type = server}) ->
esip_server_transaction:stop(Pid).
insert(Branch, Method, Type, Pid) ->
ets:update_counter(?MODULE, {transaction_number, Type}, 1),
ets:insert(?MODULE, {transaction_key(Branch, Method, Type), Pid}).
delete(Branch, Method, Type) ->
ets:update_counter(?MODULE, {transaction_number, Type}, -1),
ets:delete(?MODULE, transaction_key(Branch, Method, Type)).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
ets:new(?MODULE, [public, named_table]),
ets:insert(?MODULE, {{transaction_number, client}, 0}),
ets:insert(?MODULE, {{transaction_number, server}, 0}),
ets:insert(?MODULE, {{last_error_report, client}, 0}),
ets:insert(?MODULE, {{last_error_report, server}, 0}),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
lookup(TransactionKey) ->
case ets:lookup(?MODULE, TransactionKey) of
[{_, Pid}] ->
{ok, Pid};
_ ->
error
end.
transaction_key(Branch, <<"CANCEL">>, Type) ->
{esip:to_lower(Branch), cancel, Type};
transaction_key(Branch, _Method, Type) ->
{esip:to_lower(Branch), Type}.
pass_to_core(Core, Req, SIPSock) ->
case Core of
F when is_function(F) ->
F(Req, SIPSock, undefined);
{M, F, A} ->
apply(M, F, [Req, SIPSock, undefined | A])
end.
start_server_transaction(SIPSock, Req) ->
MaxTransactions = esip:get_config_value(max_server_transactions),
case ets:lookup(?MODULE, {transaction_number, server}) of
[{_, N}] when N >= MaxTransactions ->
maybe_report_error(server, N),
{Status, Reason} = esip:error_status({error, too_many_transactions}),
esip_transport:send(SIPSock,
esip:make_response(
Req, #sip{type = response,
status = Status,
reason = Reason},
esip:make_tag()));
_ ->
case esip_server_transaction:start(SIPSock, Req) of
{ok, _} ->
ok;
Err ->
{Status, Reason} = esip:error_status(Err),
esip_transport:send(SIPSock,
esip:make_response(
Req, #sip{type = response,
status = Status,
reason = Reason},
esip:make_tag()))
end
end.
start_client_transaction(SIPSock, Req, TU, Opts) ->
MaxTransactions = esip:get_config_value(max_client_transactions),
case ets:lookup(?MODULE, {transaction_number, client}) of
[{_, N}] when N >= MaxTransactions ->
maybe_report_error(client, N),
{error, too_many_transactions};
_ ->
esip_client_transaction:start(SIPSock, Req, TU, Opts)
end.
maybe_report_error(Type, N) ->
case ets:lookup(?MODULE, {last_error_report, Type}) of
[{_, Now}] ->
NowTime = p1_time_compat:monotonic_time(microsecond),
case NowTime - Now of
T when T > 60000000 ->
ets:insert(?MODULE, {{last_error_report, Type}, NowTime}),
?ERROR_MSG("too many ~s transactions: ~p", [Type, N]);
_ ->
ok
end;
_ ->
ok
end.