Current section

Files

Jump to
esip src esip_server_transaction.erl
Raw

src/esip_server_transaction.erl

%%%----------------------------------------------------------------------
%%% File : esip_server_transaction.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose : Server transaction layer. See RFC3261 and friends.
%%% Created : 15 Jul 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2020 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_server_transaction).
-behaviour(p1_fsm).
%% API
-export([start_link/2, start/2, stop/1, route/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,
confirmed/2]).
-include("esip.hrl").
-include("esip_lib.hrl").
-define(MAX_TRANSACTION_LIFETIME, timer:minutes(5)).
-record(state, {sock, branch, method, resp, tu}).
%%====================================================================
%% API
%%====================================================================
start_link(SIPSocket, Request) ->
p1_fsm:start_link(?MODULE, [SIPSocket, Request], []).
start(SIPSocket, Request) ->
case esip_tmp_sup:start_child(esip_server_transaction_sup,
?MODULE, p1_fsm, [SIPSocket, Request]) of
{ok, Pid} ->
{ok, make_trid(Pid)};
Err ->
Err
end.
route(Pid, R) ->
p1_fsm:send_event(Pid, R).
stop(Pid) ->
p1_fsm:send_all_state_event(Pid, stop).
%%====================================================================
%% p1_fsm callbacks
%%====================================================================
init([SIPSock, Request]) ->
Branch = esip:get_branch(Request#sip.hdrs),
State = #state{sock = SIPSock, branch = Branch},
p1_fsm:send_event(self(), Request),
erlang:send_after(?MAX_TRANSACTION_LIFETIME, self(), timeout),
esip_transaction:insert(Branch, Request#sip.method, server, self()),
{ok, trying, State}.
trying(#sip{method = <<"CANCEL">> = Method, type = request} = Req, State) ->
Resp = esip:make_response(Req,
#sip{type = response, status = 200},
esip:make_tag()),
proceeding(Resp, State#state{method = Method});
trying(#sip{type = request, method = Method} = Req, State) ->
case find_transaction_user(Req, State#state.sock) of
{dialog, TU} ->
NewState = State#state{tu = TU, method = Method},
case pass_to_transaction_user(NewState, Req) of
#sip{type = response} = Resp ->
proceeding(Resp, NewState);
wait ->
maybe_send_trying(Req),
{next_state, proceeding, NewState};
NewTU ->
case is_transaction_user(NewTU) of
true ->
maybe_send_trying(Req),
{next_state, proceeding, NewState#state{tu = NewTU}};
false ->
Resp = esip:make_response(
Req, #sip{status = 500, type = response},
esip:make_tag()),
proceeding(Resp, State#state{method = Method})
end
end;
#sip{type = response} = Resp ->
proceeding(Resp, State#state{method = Method});
cseq_out_of_order ->
Resp = esip:make_response(
Req, #sip{status = 500, type = response,
reason = <<"CSeq is Out of Order">>},
esip:make_tag()),
proceeding(Resp, State#state{method = Method});
wait ->
maybe_send_trying(Req),
{next_state, proceeding, State#state{method = Method}};
TU ->
case is_transaction_user(TU) of
true ->
maybe_send_trying(Req),
{next_state, proceeding, State#state{tu = TU,
method = Method}};
false ->
Resp = esip:make_response(
Req, #sip{status = 500, type = response},
esip:make_tag()),
proceeding(Resp, State#state{method = Method})
end
end;
trying(_Event, State) ->
{next_state, trying, State}.
proceeding({trying, TryingResp}, #state{resp = undefined} = State) ->
%% TU didn't respond in 200 ms
case send(State, TryingResp) of
ok ->
{next_state, proceeding, State};
_ ->
{stop, normal, State}
end;
proceeding(#sip{type = response, status = Status} = Resp, State) when Status < 200 ->
update_remote_seqnum(Resp, State),
case send(State, Resp) of
ok ->
{next_state, proceeding, State#state{resp = Resp}};
_ ->
{stop, normal, State}
end;
proceeding(#sip{type = response, status = Status} = Resp,
#state{method = <<"INVITE">>} = State) when Status < 300 ->
update_remote_seqnum(Resp, State),
p1_fsm:send_event_after(64*esip:timer1(), timer_L),
case send(State, Resp) of
ok ->
{next_state, accepted, State#state{resp = Resp}};
_ ->
{stop, normal, State}
end;
proceeding(#sip{type = response, status = Status} = Resp,
#state{method = <<"INVITE">>} = State) when Status >= 300 ->
update_remote_seqnum(Resp, State),
T1 = esip:timer1(),
if (State#state.sock)#sip_socket.type == udp ->
p1_fsm:send_event_after(T1, {timer_G, T1});
true ->
ok
end,
p1_fsm:send_event_after(64*T1, timer_H),
case send(State, Resp) of
ok ->
{next_state, completed, State#state{resp = Resp}};
_ ->
{stop, normal, State}
end;
proceeding(#sip{type = response, status = Status} = Resp, State) when Status >= 200 ->
update_remote_seqnum(Resp, State),
if (State#state.sock)#sip_socket.type == udp ->
p1_fsm:send_event_after(64*esip:timer1(), timer_J),
case send(State, Resp) of
ok ->
{next_state, completed, State#state{resp = Resp}};
_ ->
{stop, normal, State}
end;
true ->
send(State, Resp),
{stop, normal, State}
end;
proceeding(#sip{type = request, method = <<"CANCEL">>} = Req, State) ->
if State#state.method == <<"INVITE">> ->
case pass_to_transaction_user(State, Req) of
#sip{type = response} = Resp ->
proceeding(Resp#sip{method = <<"INVITE">>}, State);
_ ->
{next_state, proceeding, State}
end;
true ->
{next_state, proceeding, State}
end;
proceeding(#sip{type = request, method = Method} = Req,
#state{method = Method, resp = Resp} = State) ->
if Resp /= undefined ->
case send(State, Resp) of
ok ->
{next_state, proceeding, State};
_ ->
{stop, normal, State}
end;
Method == <<"INVITE">> ->
Trying = esip:make_response(Req, #sip{type = response,
status = 100}),
case send(State, Trying) of
ok ->
{next_state, proceeding, State};
_ ->
{stop, normal, State}
end;
true ->
{next_state, proceeding, State}
end;
proceeding(_Event, State) ->
{next_state, proceeding, State}.
accepted(#sip{type = request, method = <<"ACK">>} = Req, State) ->
pass_to_transaction_user(State, Req),
{next_state, accepted, State};
accepted(#sip{type = response, status = Status} = Resp, State)
when Status >= 200, Status < 300 ->
send(State, Resp),
{next_state, accepted, State};
accepted(timer_L, State) ->
{stop, normal, State};
accepted(_Event, State) ->
{next_state, accepted, State}.
completed(#sip{type = request, method = <<"ACK">>},
#state{method = <<"INVITE">>} = State) ->
if (State#state.sock)#sip_socket.type == udp ->
p1_fsm:send_event_after(esip:timer4(), timer_I),
{next_state, confirmed, State};
true ->
{stop, normal, State}
end;
completed(#sip{type = request, method = Method},
#state{method = Method, resp = Resp} = State) ->
case send(State, Resp) of
ok ->
{next_state, completed, State};
_ ->
{stop, normal, State}
end;
completed(timer_H, State) ->
pass_to_transaction_user(State, {error, timeout}),
{stop, normal, State};
completed({timer_G, T}, State) ->
T2 = esip:timer2(),
case 2*T < T2 of
true ->
p1_fsm:send_event_after(2*T, {timer_G, 2*T});
false ->
p1_fsm:send_event_after(T2, {timer_G, T2})
end,
case send(State, State#state.resp) of
ok ->
{next_state, completed, State};
_ ->
{stop, normal, State}
end;
completed(timer_J, State) ->
{stop, normal, State};
completed(_Event, State) ->
{next_state, completed, State}.
confirmed(timer_I, State) ->
{stop, normal, State};
confirmed(_Event, State) ->
{next_state, confirmed, 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, 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{branch = Branch, method = Method}) ->
esip_transaction:delete(Branch, Method, server).
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
is_transaction_user(TU) when is_function(TU) ->
true;
is_transaction_user({M, F, A}) when is_atom(M), is_atom(F), is_list(A) ->
true;
is_transaction_user(_) ->
false.
pass_to_transaction_user(#state{tu = TU, sock = Sock}, Req) ->
TrID = make_trid(),
case TU of
F when is_function(F) ->
esip:callback(F, [Req, Sock, TrID]);
{M, F, A} ->
esip:callback(M, F, [Req, Sock, TrID | A]);
#sip{type = response} = Resp ->
Resp;
_ ->
TU
end.
find_transaction_user(#sip{method = Method} = Req, SIPSock) ->
TrID = make_trid(),
case esip_dialog:id(uas, Req) of
#dialog_id{local_tag = Tag} = DialogID when Tag /= <<>> ->
case esip_dialog:lookup(DialogID) of
{ok, TU, _Dialog} when Method == <<"OPTIONS">> ->
{dialog, TU};
{ok, TU, #dialog{remote_seq_num = RemoteSeqNum}} ->
CSeq = esip:get_hdr('cseq', Req#sip.hdrs),
if is_integer(RemoteSeqNum), RemoteSeqNum > CSeq ->
cseq_out_of_order;
true ->
{dialog, TU}
end;
_ ->
esip:callback(request, [Req, SIPSock, TrID])
end;
_ ->
esip:callback(request, [Req, SIPSock, TrID])
end.
send(State, Resp) ->
case esip_transport:send(State#state.sock, Resp) of
ok ->
ok;
Err ->
pass_to_transaction_user(State, Err),
Err
end.
maybe_send_trying(#sip{method = <<"INVITE">>} = Req) ->
Trying = esip:make_response(Req, #sip{type = response,
status = 100}),
p1_fsm:send_event_after(200, {trying, Trying});
maybe_send_trying(_) ->
ok.
update_remote_seqnum(#sip{status = S}, _State) when S == 100; S >= 300 ->
ok;
update_remote_seqnum(_Resp, #state{method = <<"OPTIONS">>}) ->
ok;
update_remote_seqnum(#sip{hdrs = Hdrs} = Resp, _State) ->
DialogID = esip_dialog:id(uas, Resp),
CSeq = esip:get_hdr('cseq', Hdrs),
esip_dialog:update_remote_seqnum(DialogID, CSeq).
make_trid() ->
make_trid(self()).
make_trid(Pid) ->
#trid{owner = Pid, type = server}.