| % ITU-T Q.71x SCCP Connection-oriented Control (SCOC) |
| |
| % (C) 2010-2012 by Harald Welte <laforge@gnumonks.org> |
| % |
| % All Rights Reserved |
| % |
| % This program is free software; you can redistribute it and/or modify |
| % it under the terms of the GNU Affero General Public License as |
| % published by the Free Software Foundation; either version 3 of the |
| % License, or (at your option) any later version. |
| % |
| % This program is distributed in the hope that it will be useful, |
| % but WITHOUT ANY WARRANTY; without even the implied warranty of |
| % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| % GNU General Public License for more details. |
| % |
| % You should have received a copy of the GNU Affero General Public License |
| % along with this program. If not, see <http://www.gnu.org/licenses/>. |
| % |
| % If you modify this Program, or any covered work, by linking or |
| % combining it with runtime libraries of Erlang/OTP as released by |
| % Ericsson on http://www.erlang.org (or a modified version of these |
| % libraries), containing parts covered by the terms of the Erlang Public |
| % License (http://www.erlang.org/EPLICENSE), the licensors of this |
| % Program grant you additional permission to convey the resulting work |
| % without the need to license the runtime libraries of Erlang/OTP under |
| % the GNU Affero General Public License. Corresponding Source for a |
| % non-source form of such a combination shall include the source code |
| % for the parts of the runtime libraries of Erlang/OTP used as well as |
| % that of the covered work. |
| |
| -module(sccp_scoc). |
| -behaviour(gen_fsm). |
| |
| -include_lib("osmo_ss7/include/osmo_util.hrl"). |
| -include_lib("osmo_ss7/include/sccp.hrl"). |
| -include_lib("osmo_ss7/include/mtp3.hrl"). |
| |
| -export([start_link/1]). |
| |
| -export([init/1, handle_event/3]). |
| -export([idle/2, conn_pend_in/2, conn_pend_out/2, active/2, disconnect_pending/2, |
| reset_incoming/2, reset_outgoing/2, bothway_reset/2, wait_conn_conf/2]). |
| |
| %% gen_fsm callbacks |
| |
| % Appendix C.4 of Q.714 (all in milliseconds) |
| -define(CONNECTION_TIMER, 1 *60*100). |
| -define(TX_INACT_TIMER, 5 *60*100). |
| -define(RX_INACT_TIMER, 11 *60*100). |
| -define(RELEASE_TIMER, 10 *100). |
| -define(RELEASE_REP_TIMER, 10 *100). |
| -define(INT_TIMER, 1 *60*100). |
| -define(GUARD_TIMER, 23 *60*100). |
| -define(RESET_TIMER, 10 *100). |
| -define(REASSEMBLY_TIMER, 10 *60*100). |
| |
| -record(state, { |
| role, % client | server |
| user_application, % {MonitorRef, pid()} |
| scrc_pid, % pid() |
| rx_inact_timer, % TRef |
| tx_inact_timer, % TRef |
| local_reference, % integer() |
| remote_reference, % integer() |
| mtp3_label, % mtp3_routing_label{} |
| class, % {integer(), integer()} |
| user_pid % pid() |
| }). |
| |
| % TODO: |
| % expedited data |
| % class 3 |
| % segmentation / reassembly |
| |
| start_link(InitOpts) -> |
| gen_fsm:start_link(sccp_scoc, InitOpts, [{debug, [trace]}]). |
| |
| init(InitOpts) -> |
| LoopDat = #state{user_pid=proplists:get_value(user_pid, InitOpts), |
| scrc_pid=proplists:get_value(scrc_pid, InitOpts), |
| local_reference=proplists:get_value(local_reference, InitOpts)}, |
| io:format("SCOC init Pid=~p LoopDat ~p~n", [self(), LoopDat]), |
| {ok, idle, LoopDat}. |
| |
| handle_event(stop, _StateName, LoopDat) -> |
| io:format("SCOC received stop event~n"), |
| {stop, normal, LoopDat}; |
| handle_event({timer_expired, tx_inact_timer}, State, LoopDat) -> |
| % FIXME: T(ias) is expired, send IT message |
| io:format("T(ias) is expired, send IT message~n", []), |
| Params = [{protocol_class, LoopDat#state.class}, |
| {seq_segm, 0}, {credit, 0}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_IT, Params, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, State, LoopDat}; |
| handle_event({timer_expired, rx_inact_timer}, _State, LoopDat) -> |
| io:format("T(iar) is expired, release connection~n", []), |
| % Initiate connection release procedure |
| disc_ind_stop_rel_3(LoopDat, ?SCCP_CAUSE_REL_SCCP_FAILURE). |
| |
| % helper function to send a primitive to the user |
| send_user(_LoopDat = #state{user_pid = Pid}, Prim = #primitive{}) -> |
| Pid ! {sccp, Prim}. |
| |
| % low-level functions regarding activity timers |
| restart_tx_inact_timer(LoopDat) -> |
| timer:cancel(LoopDat#state.tx_inact_timer), |
| {ok, Tias} = timer:apply_after(?TX_INACT_TIMER, gen_fsm, send_all_state_event, |
| [self(), {timer_expired, tx_inact_timer}]), |
| LoopDat#state{tx_inact_timer = Tias}. |
| |
| restart_rx_inact_timer(LoopDat) -> |
| timer:cancel(LoopDat#state.rx_inact_timer), |
| {ok, Tiar} = timer:apply_after(?RX_INACT_TIMER, gen_fsm, send_all_state_event, |
| [self(), {timer_expired, rx_inact_timer}]), |
| LoopDat#state{rx_inact_timer = Tiar}. |
| |
| start_inact_timers(LoopDat) -> |
| {ok, Tias} = timer:apply_after(?TX_INACT_TIMER, gen_fsm, send_all_state_event, |
| [self(), {timer_expired, tx_inact_timer}]), |
| {ok, Tiar} = timer:apply_after(?RX_INACT_TIMER, gen_fsm, send_all_state_event, |
| [self(), {timer_expired, rx_inact_timer}]), |
| LoopDat#state{rx_inact_timer = Tiar, tx_inact_timer = Tias}. |
| |
| stop_inact_timers(#state{rx_inact_timer = Tiar, tx_inact_timer = Tias}) -> |
| timer:cancel(Tiar), |
| timer:cancel(Tias). |
| |
| |
| % -spec idle(#primitive{} | ) -> gen_fsm_state_return(). |
| |
| % STATE Idle |
| |
| % N-CONNECT.req from user |
| idle(#primitive{subsystem = 'N', gen_name = 'CONNECT', |
| spec_name = request, parameters = Param}, LoopDat) -> |
| % local reference already assigned in SCRC when instantiating this SCOC |
| LocalRef = LoopDat#state.local_reference, |
| % FIXME: determine protocol class and credit |
| Class = {2,0}, |
| ParamDown = Param ++ [{src_local_ref, LocalRef}, {protocol_class, Class}], |
| gen_fsm:send_event(LoopDat#state.scrc_pid, |
| osmo_util:make_prim('OCRC','CONNECTION', indication, ParamDown)), |
| % start connection timer |
| {next_state, conn_pend_out, LoopDat#state{class = Class}, ?CONNECTION_TIMER}; |
| |
| % RCOC-CONNECTION.req from SCRC |
| idle(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION', |
| spec_name = indication, parameters = Params}, LoopDat) -> |
| % associate remote reference to connection section |
| RemRef = proplists:get_value(src_local_ref, Params), |
| % determine the MTP3 label from Calling Party and/or MTP3 header |
| Mtp3Label = determine_m3l_from_cr(Params), |
| % determine protocol class and FIXME: credit |
| Class = proplists:get_value(protocol_class, Params), |
| LoopDat1 = LoopDat#state{remote_reference = RemRef, class = Class, |
| mtp3_label = mtp3_codec:invert_rout_lbl(Mtp3Label)}, |
| case LoopDat1#state.user_pid of |
| undefined -> |
| io:format("CR to unequipped subsystem!~n"), |
| RefParam = [{refusal_cause, ?SCCP_CAUSE_REF_UNEQUIPPED_USER}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_CREF, RefParam, LoopDat1), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, idle, LoopDat1}; |
| _ -> |
| % send N-CONNECT.ind to user |
| send_user(LoopDat1, osmo_util:make_prim('N', 'CONNECT', indication, [{scoc_pid, self()}|Params])), |
| {next_state, conn_pend_in, LoopDat1} |
| end; |
| |
| % RCOC-ROUTING_FAILURE.ind from SCRC |
| idle(#primitive{subsystem = 'RCOC', gen_name = 'ROUTING FAILURE', |
| spec_name = indication}, LoopDat) -> |
| gen_fsm:send_event(LoopDat#state.scrc_pid, |
| osmo_util:make_prim('OCRC', 'CONNECTION REFUSED', indication)), |
| {next_state, idle, LoopDat}; |
| |
| %FIXME: request type 2 ?!? |
| |
| % RCOC-RELEASED.ind from SCRC |
| idle(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLSD}}, LoopDat) -> |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLC, [], LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, idle, LoopDat}; |
| |
| % RCOC-RELEASE_COMPLETE.ind from SCRC |
| idle(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLC}}, LoopDat) -> |
| {next_state, idle, LoopDat}; |
| |
| idle(#primitive{subsystem= 'RCOC', gen_name = 'DATA', |
| spec_name = indication, parameters = Param}, LoopDat) -> |
| % FIXME: if source reference, send error |
| send_user(LoopDat, osmo_util:make_prim('N', 'DATA', indication, Param)), |
| {next_state, idle, LoopDat}. |
| |
| % STATE Connection pending incoming |
| conn_pend_in(#primitive{subsystem = 'N', gen_name = 'CONNECT', |
| spec_name = response, parameters = Param}, LoopDat) -> |
| io:format("SCOC N-CONNECT.resp LoopDat ~p~n", [LoopDat]), |
| % assign local reference, SLS, protocol class and credit for inc section |
| OutParam = [{dst_local_ref, LoopDat#state.remote_reference}, |
| {src_local_ref, LoopDat#state.local_reference}, |
| {protocol_class, LoopDat#state.class}] ++ Param, |
| gen_fsm:send_event(LoopDat#state.scrc_pid, |
| osmo_util:make_prim('OCRC', 'CONNECTION', confirm, OutParam)), |
| % start inactivity timers |
| LoopDat1 = start_inact_timers(LoopDat), |
| {next_state, active, LoopDat1}; |
| conn_pend_in(any_npdu_type, LoopDat) -> |
| {next_state, conn_pend_in, LoopDat}; |
| conn_pend_in(#primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = request, parameters = Param}, LoopDat) -> |
| % release resourcers (local ref may have to be released an frozen) |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_CREF, Param, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, idle, LoopDat}. |
| |
| |
| disc_ind_stop_rel_3(LoopDat, RelCause) -> |
| Params = [{release_cause, RelCause}], |
| % send N-DISCONNECT.ind to user |
| send_user(LoopDat, osmo_util:make_prim('N', 'DISCONNECT',indication, Params)), |
| % stop inactivity timers |
| stop_inact_timers(LoopDat), |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLSD, Params, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % start release timer |
| {next_state, disconnect_pending, LoopDat, ?RELEASE_TIMER}. |
| |
| rel_res_disc_ind_idle_2(LoopDat, Params) -> |
| % release resources and local reference (freeze) |
| % send N-DISCONNECT.ind to user |
| send_user(LoopDat, osmo_util:make_prim('N', 'DISCONNECT', indication, Params)), |
| {next_state, idle, LoopDat}. |
| |
| |
| % STATE Connection pending outgoing |
| conn_pend_out(#primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = request}, LoopDat) -> |
| % FIXME: what about the connection timer ? |
| {next_state, wait_conn_conf, LoopDat}; |
| conn_pend_out(timeout, LoopDat) -> |
| rel_res_disc_ind_idle_2(LoopDat, [{refusal_cause, ?SCCP_CAUSE_REF_EXP_CONN_EST_TMR}]); |
| conn_pend_out(routing_failure, LoopDat) -> |
| rel_res_disc_ind_idle_2(LoopDat, [{refusal_cause, ?SCCP_CAUSE_REF_DEST_INACCESS}]); |
| conn_pend_out(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLSD, |
| parameters = Params}}, LoopDat) -> |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLC, [], LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| rel_res_disc_ind_idle_2(LoopDat, Params); |
| % other N-PDU Type |
| conn_pend_out(other_npdu_type, LoopDat) -> |
| rel_res_disc_ind_idle_2(LoopDat, [{refusal_cause, ?SCCP_CAUSE_REF_INCOMP_USER_DATA}]); |
| conn_pend_out(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_CREF, |
| parameters = Params}}, LoopDat) -> |
| rel_res_disc_ind_idle_2(LoopDat, Params); |
| conn_pend_out(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_CC, |
| parameters = Params}}, LoopDat) -> |
| % start inactivity timers |
| LoopDat1 = start_inact_timers(LoopDat), |
| % assign protocol class and associate remote reference to connection |
| SrcLocalRef = proplists:get_value(src_local_ref, Params), |
| Mtp3Label = proplists:get_value(mtp3_label, Params), |
| LoopDat2 = LoopDat1#state{remote_reference = SrcLocalRef, |
| mtp3_label = mtp3_codec:invert_rout_lbl(Mtp3Label)}, |
| % send N-CONNECT.conf to user |
| send_user(LoopDat2, #primitive{subsystem = 'N', gen_name = 'CONNECT', |
| spec_name = confirm, parameters = Params}), |
| {next_state, active, LoopDat2}. |
| |
| stop_c_tmr_rel_idle_5(LoopDat) -> |
| % stop connection timer (implicit) |
| % release resources and local reference |
| {next_state, idle, LoopDat}. |
| |
| rel_freeze_idle(LoopDat) -> |
| {next_state, idle, LoopDat}. |
| |
| % STATE Wait connection confirmed |
| wait_conn_conf(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLSD}}, LoopDat) -> |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLC, [], LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| stop_c_tmr_rel_idle_5(LoopDat); |
| wait_conn_conf(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_CC, |
| parameters = Params}}, LoopDat) -> |
| % stop connection timer (implicit) |
| % associate remote reference to connection section |
| % assign protocol class and associate remote reference to connection |
| SrcLocalRef = proplists:get_value(src_local_ref, Params), |
| Mtp3Label = proplists:get_value(mtp3_label, Params), |
| LoopDat2 = LoopDat#state{remote_reference = SrcLocalRef, |
| mtp3_label = mtp3_codec:invert_rout_lbl(Mtp3Label)}, |
| relsd_tmr_disc_pend_6(LoopDat2, ?SCCP_CAUSE_REL_USER_ORIG); |
| wait_conn_conf(other_npdu_type, LoopDat) -> |
| % stop connection timer (implicit) |
| rel_freeze_idle(LoopDat); |
| wait_conn_conf(timeout, LoopDat) -> |
| stop_c_tmr_rel_idle_5(LoopDat); |
| wait_conn_conf(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_CREF}}, LoopDat) -> |
| stop_c_tmr_rel_idle_5(LoopDat); |
| wait_conn_conf(routing_failure, LoopDat) -> |
| stop_c_tmr_rel_idle_5(LoopDat). |
| |
| |
| relsd_tmr_disc_pend_6(LoopDat, RelCause) -> |
| Params = [{release_cause, RelCause}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLSD, Params, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % start release timer |
| {next_state, disconnect_pending, LoopDat, ?RELEASE_TIMER}. |
| |
| % STATE Active |
| active(#primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = request}, LoopDat) -> |
| % stop inactivity timers |
| LoopDat1 = start_inact_timers(LoopDat), |
| relsd_tmr_disc_pend_6(LoopDat1, ?SCCP_CAUSE_REL_USER_ORIG); |
| active(internal_disconnect, LoopDat) -> |
| disc_ind_stop_rel_3(LoopDat, ?SCCP_CAUSE_REL_SCCP_FAILURE); |
| active(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| parameters = #sccp_msg{msg_type = MsgType}}, LoopDat) |
| when MsgType == ?SCCP_MSGT_CREF; |
| MsgType == ?SCCP_MSGT_CC; |
| MsgType == ?SCCP_MSGT_RLC -> |
| % restart receive inactivity timer |
| LoopDat1 = restart_rx_inact_timer(LoopDat), |
| {next_state, active, LoopDat1}; |
| active(#primitive{subsystem = 'RCOC', gen_name ='CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLSD, |
| parameters = Params}}, LoopDat) -> |
| % send N-DISCONNECT.ind to user |
| send_user(LoopDat, #primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = indication, parameters = Params}), |
| % release resources and local reference (freeze) |
| % stop inactivity timers |
| stop_inact_timers(LoopDat), |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLC, [], LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, idle, LoopDat}; |
| active(error, LoopDat) -> |
| % send N-DISCONNECT.ind to user |
| send_user(LoopDat, #primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = indication}), |
| % release resources and local reference (freeze) |
| % stop inactivity timers |
| stop_inact_timers(LoopDat), |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLC, [], LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| {next_state, idle, LoopDat}; |
| %active(rcv_inact_tmr_exp, LoopDat) -> |
| % this is handled in the global handle_event() above |
| active(routing_failure, LoopDat) -> |
| % send N-DISCONNECT.ind to user |
| send_user(LoopDat, #primitive{subsystem = 'N', gen_name = 'DISCONNECT', |
| spec_name = indication}), |
| % stop inactivity timers |
| stop_inact_timers(LoopDat), |
| % start release timer |
| {next_state, disconnect_pending, LoopDat, ?RELEASE_TIMER}; |
| % Connection release procedures at destination node |
| %active(internal_disconnect) -> |
| % Data transfer procedures |
| active(#primitive{subsystem = 'N', gen_name = 'DATA', |
| spec_name = request, parameters = Param}, LoopDat) -> |
| % FIXME Segment NSDU and assign value to bit M |
| % FIXME handle protocol class 3 |
| gen_fsm:send_event(LoopDat#state.scrc_pid, {dt1, []}), |
| % restart send inactivity timer |
| LoopDat1 = restart_tx_inact_timer(LoopDat), |
| {next_state, active, LoopDat1}; |
| active(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_DT1, |
| parameters = Params}}, LoopDat) -> |
| % restart receive inactivity timer |
| LoopDat1 = restart_rx_inact_timer(LoopDat), |
| % FIXME handle protocol class 3 |
| % FIXME check for M-bit=1 and put data in Rx queue |
| % N-DATA.ind to user |
| UserData = proplists:get_value(user_data, Params), |
| send_user(LoopDat1, osmo_util:make_prim('N', 'DATA', indication, {user_data, UserData})), |
| {next_state, active, LoopDat1}; |
| % Reset procedures |
| active(#primitive{subsystem = 'N', gen_name = 'RESET', |
| spec_name = request, parameters = _Param}, LoopDat) -> |
| CausePar = [{reset_cause, ?SCCP_CAUSE_RES_ENDU_ORIGINATED}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RSR, CausePar, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % start reset timer (implicit next_state below) |
| % restart send inact timer |
| LoopDat1 = restart_tx_inact_timer(LoopDat), |
| % reset variables and discard all queued and unacked msgs |
| {next_state, reset_outgoing, LoopDat1, ?RESET_TIMER}; |
| active(internal_reset_req, LoopDat) -> |
| CausePar = [{reset_cause, ?SCCP_CAUSE_RES_SCCP_USER_ORIG}], |
| % N-RESET.ind to user |
| send_user(LoopDat, osmo_util:make_prim('N', 'RESET', indication, |
| CausePar)), |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RSR, CausePar, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % start reset timer |
| % restart send inact timer |
| LoopDat1 = restart_tx_inact_timer(LoopDat), |
| % reset variables and discard all queued and unacked msgs |
| {next_state, bothway_reset, LoopDat1, ?RESET_TIMER}; |
| active(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_IT, |
| parameters = Params}}, LoopDat) -> |
| % restart receive inactivity timer |
| LoopDat1 = restart_rx_inact_timer(LoopDat), |
| % Section 3.4 Inactivity control |
| SrcRef = proplists:get_value(src_local_ref, Params), |
| case LoopDat1#state.remote_reference of |
| SrcRef -> |
| ClassOpt = proplists:get_value(protocol_class, Params), |
| case LoopDat1#state.class of |
| ClassOpt -> |
| % FIXME: class3: discrepancy in seq/segm or credit -> reset |
| {next_state, active, LoopDat1}; |
| _ -> |
| % discrepancy in class -> release |
| disc_ind_stop_rel_3(LoopDat1, ?SCCP_CAUSE_REL_INCONS_CONN_DAT) |
| end; |
| _ -> |
| % discrepancy in src ref -> release |
| disc_ind_stop_rel_3(LoopDat1, ?SCCP_CAUSE_REL_INCONS_CONN_DAT) |
| end; |
| active(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RSC}}, LoopDat) -> |
| % discard received message |
| {next_state, active, LoopDat}; |
| active(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RSR, |
| parameters = Params}}, LoopDat) -> |
| % restart send inactivity timer |
| LoopDat1 = restart_tx_inact_timer(LoopDat), |
| % N-RESET.ind to user |
| send_user(LoopDat1, osmo_util:make_prim('N', 'RESET', indication, Params)), |
| % reset variables and discard all queued and unacked msgs |
| {next_state, reset_incoming, LoopDat1}. |
| |
| rel_res_stop_tmr_12(LoopDat) -> |
| % release resources and local reference (freeze) |
| % stop release and interval timers |
| {next_state, idle, LoopDat}. |
| |
| % STATE Disconnect pending |
| disconnect_pending(#primitive{subsystem = 'RCOC', gen_name = 'CONNECTION-MSG', |
| spec_name = indication, |
| parameters = #sccp_msg{msg_type = ?SCCP_MSGT_RLC}}, LoopDat) -> |
| rel_res_stop_tmr_12(LoopDat); |
| disconnect_pending(released_error, LoopDat) -> |
| rel_res_stop_tmr_12(LoopDat); |
| disconnect_pending(routing_failure, LoopDat) -> |
| {next_state, disconnect_pending, LoopDat}; |
| disconnect_pending(other_npdu_type, LoopDat) -> |
| % discared received message |
| {next_state, disconnect_pending, LoopDat}; |
| disconnect_pending(timeout, LoopDat) -> |
| % FIXME: store the original release cause and use same cause here |
| Params = [{release_cause, ?SCCP_CAUSE_REL_UNQUALIFIED}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLSD, Params, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % FIXME: start interval timer |
| % start repeat release timer |
| {next_state, disconnect_pending, ?RELEASE_REP_TIMER}; |
| disconnect_pending(intv_tmr_exp, LoopDat) -> |
| % inform maintenance |
| rel_res_stop_tmr_12(LoopDat); |
| % FIXME: this is currently ending up in normal 'timeout' above |
| disconnect_pending(repeat_release_tmr_exp, LoopDat) -> |
| Params = [{release_cause, ?SCCP_CAUSE_REL_UNQUALIFIED}], |
| Prim = gen_co_sccp_prim(?SCCP_MSGT_RLSD, Params, LoopDat), |
| gen_fsm:send_event(LoopDat#state.scrc_pid, Prim), |
| % FIXME restart repeat release timer |
| {next_state, disconnect_pending}. |
| |
| res_out_res_conf_req(LoopDat) -> |
| % N-RESET.conf to user |
| send_user(LoopDat, osmo_util:make_prim('N', 'RESET', confirm)), |
| % stop reset timer (implicit) |
| % restart receive inactivity timer |
| LoopDat1 = restart_rx_inact_timer(LoopDat), |
| % resume data transfer |
| {next_state, active, LoopDat1}. |
| |
| % STATE Reset outgoing |
| reset_outgoing(#primitive{subsystem = 'N', gen_name = 'DATA', |
| spec_name = request, parameters = Params}, LoopDat) -> |
| % FIXME received information ?!? |
| {next_state, reset_outgoing, LoopDat}; |
| reset_outgoing(#primitive{subsystem = 'N', gen_name = 'EXPEDITED DATA', |
| spec_name = request, parameters = Params}, LoopDat) -> |
| % FIXME received information ?!? |
| {next_state, reset_outgoing, LoopDat}; |
| reset_outgoing(timeout, LoopDat) -> |
| % FIXME check for temporary connection section |
| % inform maintenance |
| {next_state, maintenance_Blocking, LoopDat}; |
| %reset_outgoing(error, LoopDat) -> |
| %reset_outgoing(released, LoopDat) -> |
| reset_outgoing(other_npdu_type, LoopDat) -> |
| % discard received message |
| {next_state, reset_outgoing, LoopDat}; |
| reset_outgoing(reset_confirm, LoopDat) -> |
| res_out_res_conf_req(LoopDat); |
| reset_outgoing(reset_request, LoopDat) -> |
| res_out_res_conf_req(LoopDat). |
| |
| bway_res_req_resp(LoopDat) -> |
| {next_state, reset_outgoing, LoopDat}. |
| |
| bway_res_res_conf_req(LoopDat) -> |
| % N-RESET.conf to user |
| send_user(LoopDat, #primitive{subsystem = 'N', gen_name = 'RESET', |
| spec_name = confirm}), |
| % stop reset timer (implicit) |
| % restart receive inactivity timer |
| LoopDat1 = restart_rx_inact_timer(LoopDat), |
| {next_state, reset_incoming, LoopDat1}. |
| |
| % STATE Bothway Reset |
| bothway_reset(#primitive{subsystem = 'N', gen_name = 'RESET', |
| spec_name = request, parameters = Params}, LoopDat) -> |
| bway_res_req_resp(LoopDat); |
| bothway_reset(#primitive{subsystem = 'N', gen_name = 'RESET', |
| spec_name = response, parameters = Params}, LoopDat) -> |
| bway_res_req_resp(LoopDat); |
| bothway_reset(timeout, LoopDat) -> |
| % FIXME check for temporary connection section |
| % inform maintenance |
| {next_state, maintenance_Blocking, LoopDat}; |
| %bothway_reset(error, LoopDat) -> |
| %bothway_reset(released, LoopDat) -> |
| bothway_reset(other_npdu_type, LoopDat) -> |
| % discard received message |
| {next_state, bothway_reset, LoopDat}. |
| |
| % STATE Reset incoming |
| reset_incoming(#primitive{subsystem = 'N', gen_name = 'RESET', |
| spec_name = request, parameters = Params}, LoopDat) -> |
| % received information |
| {nest_state, reset_incoming, LoopDat}; |
| %reset_incoming(error, LoopDat) -> |
| %reset_incoming(released, LoopDat) -> |
| reset_incoming(other_npdu_type, LoopDat) -> |
| % discard received message |
| % internal reset request |
| {next_state, active, LoopDat}. |
| % FIXME: response or request |
| %reset_incoming( |
| |
| |
| msg_has(MsgType, src_local_ref, LoopDat) when |
| MsgType == ?SCCP_MSGT_CR; |
| MsgType == ?SCCP_MSGT_CC; |
| MsgType == ?SCCP_MSGT_RLSD; |
| MsgType == ?SCCP_MSGT_RLC; |
| MsgType == ?SCCP_MSGT_RSR; |
| MsgType == ?SCCP_MSGT_RSC; |
| MsgType == ?SCCP_MSGT_IT -> |
| [{src_local_ref, LoopDat#state.local_reference}]; |
| msg_has(MsgType, dst_local_ref, LoopDat) when |
| MsgType == ?SCCP_MSGT_CR; |
| MsgType == ?SCCP_MSGT_CC; |
| MsgType == ?SCCP_MSGT_CREF; |
| MsgType == ?SCCP_MSGT_RLSD; |
| MsgType == ?SCCP_MSGT_RLC; |
| MsgType == ?SCCP_MSGT_DT1; |
| MsgType == ?SCCP_MSGT_DT2; |
| MsgType == ?SCCP_MSGT_AK; |
| MsgType == ?SCCP_MSGT_ED; |
| MsgType == ?SCCP_MSGT_RSR; |
| MsgType == ?SCCP_MSGT_RSC; |
| MsgType == ?SCCP_MSGT_ERR; |
| MsgType == ?SCCP_MSGT_IT -> |
| [{dst_local_ref, LoopDat#state.remote_reference}]; |
| msg_has(MsgType, _, _LoopDat) -> |
| []. |
| |
| % generate a Connection Oriented SCCP message, automatically adding src and dst |
| % local reference if required for the specific message type |
| gen_co_sccp(MsgType, ParamsIn, LoopDat) when is_record(LoopDat, state) -> |
| Params = msg_has(MsgType, src_local_ref, LoopDat) ++ |
| msg_has(MsgType, dst_local_ref, LoopDat), |
| #sccp_msg{msg_type = MsgType, parameters = ParamsIn ++ Params}. |
| |
| % generate a OCRC primitive containing a connection oriented SCCP message |
| gen_co_sccp_prim(MsgType, ParamsIn, LoopDat) when is_record(LoopDat, state) -> |
| Label = LoopDat#state.mtp3_label, |
| Sccp = gen_co_sccp(MsgType, ParamsIn, LoopDat), |
| osmo_util:make_prim('OCRC', 'CONNECTION-MSG', request, [Sccp, Label]). |
| |
| % According to Q.714 2.7 d) |
| determine_m3l_from_cr(Params) -> |
| M3l = proplists:get_value(mtp3_label, Params), |
| % if there is no calling party, or no point code in the calling party, |
| % we have to use the MTP3 OPC as point code for the 'connection section' |
| case proplists:get_value(calling_party_addr, Params) of |
| undefined -> |
| M3l; |
| #sccp_addr{point_code = undefined} -> |
| M3l; |
| #sccp_addr{point_code = Spc} -> |
| M3l#mtp3_routing_label{origin_pc = Spc} |
| end. |