Current section

Files

Jump to
esip src esip_client_transaction.erl
Raw

src/esip_client_transaction.erl

%%%----------------------------------------------------------------------
%%% File : esip_client_transaction.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose :
%%% Created : 20 Dec 2010 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_client_transaction).
-behaviour(p1_fsm).
%% API
-export([start_link/4, start/3, start/4, stop/1, route/2, cancel/2]).
%% p1_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4,
handle_info/3, terminate/3, code_change/4]).
%% p1_fsm states
-export([trying/2,
proceeding/2,
accepted/2,
completed/2]).
-include("esip.hrl").
-include("esip_lib.hrl").
-define(MAX_TRANSACTION_LIFETIME, timer:minutes(5)).
-record(state, {req, tu, sock, branch, cancelled = false}).
%%%===================================================================
%%% API
%%%===================================================================
start_link(SIPSocket, Request, TU, Opts) ->
p1_fsm:start_link(?MODULE, [SIPSocket, Request, TU, Opts], []).
start(SIPSocket, Request, TU) ->
start(SIPSocket, Request, TU, []).
start(SIPSocket, Request, TU, Opts) ->
case esip_tmp_sup:start_child(
esip_client_transaction_sup,
?MODULE, p1_fsm, [SIPSocket, Request, TU, Opts]) of
{ok, Pid} ->
{ok, make_trid(Pid)};
{error, _} = Err ->
Err
end.
route(Pid, R) ->
p1_fsm:send_event(Pid, R).
cancel(Pid, TU) ->
p1_fsm:send_event(Pid, {cancel, TU}).
stop(Pid) ->
p1_fsm:send_all_state_event(Pid, stop).
%%%===================================================================
%%% p1_fsm callbacks
%%%===================================================================
init([SIPSocket, Request, TU, _Opts]) ->
p1_fsm:send_event(self(), Request),
erlang:send_after(?MAX_TRANSACTION_LIFETIME, self(), timeout),
{ok, trying, #state{tu = TU, sock = SIPSocket}}.
trying(#sip{type = request, hdrs = Hdrs, method = Method} = Request,
#state{sock = #sip_socket{type = Type}} = State) ->
Branch = esip:get_branch(Hdrs),
esip_transaction:insert(Branch, Method, client, self()),
T1 = esip:timer1(),
if Type == udp, Method == <<"INVITE">> ->
p1_fsm:send_event_after(T1, {timer_A, T1});
Type == udp ->
p1_fsm:send_event_after(T1, {timer_E, T1});
true ->
ok
end,
if Method == <<"INVITE">> ->
p1_fsm:send_event_after(64*T1, timer_B);
true ->
p1_fsm:send_event_after(64*T1, timer_F)
end,
NewState = State#state{branch = Branch, req = Request},
case send(NewState, Request) of
ok ->
{next_state, trying, NewState};
_ ->
{stop, normal, NewState}
end;
trying({timer_A, T}, State) ->
p1_fsm:send_event_after(2*T, {timer_A, 2*T}),
case send(State, State#state.req) of
ok ->
{next_state, trying, State};
_ ->
{stop, normal, State}
end;
trying({timer_E, T}, State) ->
T4 = esip:timer4(),
case 2*T < T4 of
true ->
p1_fsm:send_event_after(2*T, {timer_E, 2*T});
false ->
p1_fsm:send_event_after(T4, {timer_E, T4})
end,
case send(State, State#state.req) of
ok ->
{next_state, trying, State};
_ ->
{stop, normal, State}
end;
trying(Timer, State) when Timer == timer_B; Timer == timer_F ->
pass_to_transaction_user(State, {error, timeout}),
{stop, normal, State};
trying(#sip{type = response} = Resp, State) ->
case State#state.cancelled of
{true, TU} ->
p1_fsm:send_event(self(), {cancel, TU});
_ ->
ok
end,
proceeding(Resp, State);
trying({cancel, TU}, State) ->
{next_state, trying, State#state{cancelled = {true, TU}}};
trying(_Event, State) ->
{next_state, trying, State}.
proceeding(#sip{type = response, status = Status} = Resp, State) when Status < 200 ->
pass_to_transaction_user(State, Resp),
{next_state, proceeding, State};
proceeding(#sip{type = response, status = Status} = Resp,
#state{req = #sip{method = <<"INVITE">>}} = State) when Status < 300 ->
pass_to_transaction_user(State, Resp),
p1_fsm:send_event_after(64*esip:timer1(), timer_M),
{next_state, accepted, State};
proceeding(#sip{type = response, status = Status} = Resp,
#state{req = #sip{method = <<"INVITE">>}} = State) when Status >= 300 ->
pass_to_transaction_user(State, Resp),
if (State#state.sock)#sip_socket.type == udp ->
p1_fsm:send_event_after(64*esip:timer1(), timer_D),
case send_ack(State, Resp) of
ok ->
{next_state, completed, State};
_ ->
{stop, normal, State}
end;
true ->
send_ack(State, Resp),
{stop, normal, State}
end;
proceeding(#sip{type = response} = Resp, State) ->
pass_to_transaction_user(State, Resp),
if (State#state.sock)#sip_socket.type == udp ->
p1_fsm:send_event_after(esip:timer4(), timer_K),
{next_state, completed, State};
true ->
{stop, normal, State}
end;
proceeding({timer_E, T}, State) ->
p1_fsm:send_event_after(esip:timer2(), {timer_E, T}),
case send(State, State#state.req) of
ok ->
{next_state, proceeding, State};
_ ->
{stop, normal, State}
end;
proceeding(timer_F, State) ->
pass_to_transaction_user(State, {error, timeout}),
{stop, normal, State};
proceeding({cancel, TU}, #state{req = #sip{hdrs = Hdrs} = Req} = State) ->
Hdrs1 = esip:filter_hdrs(['call-id', 'to', 'from', 'cseq',
'max-forwards', 'route'], Hdrs),
[Via|_] = esip:get_hdrs('via', Hdrs),
Hdrs2 = case esip:get_config_value(software) of
undefined ->
[{'via', [Via]}|Hdrs1];
UA ->
[{'via', [Via]},{'user-agent', UA}|Hdrs1]
end,
CancelReq = #sip{type = request,
method = <<"CANCEL">>,
uri = Req#sip.uri,
hdrs = Hdrs2},
esip_client_transaction:start(State#state.sock, CancelReq, TU),
{next_state, proceeding, State};
proceeding(_Event, State) ->
{next_state, proceeding, State}.
accepted(timer_M, State) ->
{stop, normal, State};
accepted(#sip{type = response, status = Status} = Resp, State)
when Status >= 200, Status < 300 ->
pass_to_transaction_user(State, Resp),
{next_state, accepted, State};
accepted(_Event, State) ->
{next_state, accepted, State}.
completed(timer_D, State) ->
{stop, normal, State};
completed(timer_K, State) ->
{stop, normal, State};
completed(#sip{type = response, status = Status} = Resp, State) when Status >= 300 ->
case send_ack(State, Resp) of
ok ->
{next_state, completed, State};
_ ->
{stop, normal, State}
end;
completed(_Event, State) ->
{next_state, completed, State}.
handle_event(stop, _StateName, State) ->
{stop, normal, State};
handle_event(_Event, StateName, State) ->
{next_state, StateName, State}.
handle_sync_event(_Event, _From, StateName, State) ->
Reply = ok,
{reply, Reply, StateName, State}.
handle_info(timeout, _StateName, State) ->
pass_to_transaction_user(State, {error, timeout}),
{stop, normal, State};
handle_info(_Info, StateName, State) ->
{next_state, StateName, State}.
terminate(_Reason, _StateName, #state{req = Req, branch = Branch}) ->
if Req /= undefined ->
catch esip_transaction:delete(Branch, Req#sip.method, client);
true ->
ok
end.
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
pass_to_transaction_user(#state{tu = TU, sock = Sock}, Resp) ->
TrID = make_trid(),
case TU of
F when is_function(F) ->
esip:callback(F, [Resp, Sock, TrID]);
{M, F, A} ->
esip:callback(M, F, [Resp, Sock, TrID | A]);
_ ->
TU
end.
send_ack(#state{req = #sip{uri = URI, hdrs = Hdrs,
method = <<"INVITE">>}} = State, Resp) ->
Hdrs1 = esip:filter_hdrs(['call-id', 'from', 'cseq',
'route', 'max-forwards',
'authorization',
'proxy-authorization'], Hdrs),
To = esip:get_hdr('to', Resp#sip.hdrs),
[Via|_] = esip:get_hdrs('via', Hdrs),
Hdrs2 = case esip:get_config_value(software) of
undefined ->
[{'via', [Via]},{'to', To}|Hdrs1];
Software ->
[{'via', [Via]},{'to', To},{'user-agent', Software}|Hdrs1]
end,
ACK = #sip{type = request,
uri = URI,
method = <<"ACK">>,
hdrs = Hdrs2},
send(State, ACK);
send_ack(_, _) ->
ok.
send(State, Resp) ->
case esip_transport:send(State#state.sock, Resp) of
ok ->
ok;
{error, _} = Err ->
pass_to_transaction_user(State, Err),
Err
end.
make_trid() ->
make_trid(self()).
make_trid(Pid) ->
#trid{owner = Pid, type = client}.