| % SCCP routing control procedures (SCRC) |
| |
| % (C) 2010-2011 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_scrc). |
| -behaviour(gen_fsm). |
| -export([start_link/1, init/1, terminate/3, idle/2, handle_info/3]). |
| |
| -include_lib("osmo_ss7/include/osmo_util.hrl"). |
| -include_lib("osmo_ss7/include/sccp.hrl"). |
| -include_lib("osmo_ss7/include/mtp3.hrl"). |
| |
| |
| |
| -record(scrc_state, { |
| scoc_conn_ets, |
| next_local_ref, |
| sup_pid % pid() of the supervisor |
| }). |
| % TODO: Integrate with proper SCCP routing / GTT implementation |
| |
| tx_prim_to_local_ref(Prim, LocalRef) -> |
| % determine the Pid to which the primitive must be sent |
| ConnTable = get(scoc_by_ref), |
| case ets:lookup(ConnTable, LocalRef) of |
| [{LocalRef, ScocPid}] -> |
| gen_fsm:send_event(ScocPid, Prim); |
| _ -> |
| io:format("Primitive ~p for unknown local reference ~p~n", |
| [Prim, LocalRef]) |
| end. |
| |
| |
| start_link(InitData) -> |
| % make sure to store the Pid of the caller in the scrc_state |
| gen_fsm:start_link({local, sccp_scrc}, sccp_scrc, |
| [{sup_pid,self()}|InitData], [{debug, [trace]}]). |
| |
| % gen_fsm init callback, called by start_link() |
| init(InitPropList) -> |
| io:format("SCRC Init PropList~p ~n", [InitPropList]), |
| UserPid = proplists:get_value(sup_pid, InitPropList), |
| LoopData = #scrc_state{sup_pid = UserPid, next_local_ref = 0}, |
| TableRef = ets:new(scoc_by_ref, [set]), |
| put(scoc_by_ref, TableRef), |
| ok = ss7_links:bind_service(?MTP3_SERV_SCCP, "osmo_sccp"), |
| {ok, idle, LoopData}. |
| |
| |
| terminate(Reason, _State, _LoopDat) -> |
| io:format("SCRC: Terminating with reason ~p~n", [Reason]), |
| Tref = get(scoc_by_ref), |
| ets:delete(Tref), |
| ok. |
| |
| % helper function to create new SCOC instance |
| spawn_new_scoc(LoopDat, UserPid) when is_record(LoopDat, scrc_state) -> |
| % create new SCOC instance |
| % Compute the new local reference |
| LocalRef = LoopDat#scrc_state.next_local_ref + 1, |
| LoopDat1 = LoopDat#scrc_state{next_local_ref = LocalRef}, |
| % generate proplist for SCRC initialization |
| ScocPropList = [{scrc_pid, self()}, {user_pid, UserPid}, {local_reference, LocalRef}], |
| % FIXME: we should rather ask the supervisor to start it on our behalf |
| {ok, ScocPid} = sccp_scoc:start_link(ScocPropList), |
| % insert SCOC instance in connection table |
| ConnTable = get(scoc_by_ref), |
| ets:insert_new(ConnTable, {LocalRef, ScocPid}), |
| {LoopDat1, ScocPid}. |
| |
| is_cr_or_connless(SccpMsg) when is_record(SccpMsg, sccp_msg) -> |
| case SccpMsg of |
| #sccp_msg{msg_type = ?SCCP_MSGT_CR} -> |
| true; |
| _ -> |
| sccp_codec:is_connectionless(SccpMsg) |
| end. |
| |
| % deliver message to local SCOC or SCLC |
| deliver_to_scoc_sclc(LoopDat, Msg, UserPid) when is_record(Msg, sccp_msg), |
| is_record(LoopDat, scrc_state) -> |
| case Msg of |
| % special handling for CR message here in SCRC |
| #sccp_msg{msg_type = ?SCCP_MSGT_CR} -> |
| % spawn a new SCOC instance/process |
| {LoopDat1, ScocPid} = spawn_new_scoc(LoopDat, UserPid), |
| % send a RCOC-CONNECTING.ind primitive to the new SCOC fsm |
| UserPrim = osmo_util:make_prim('RCOC','CONNECTION', indication, Msg#sccp_msg.parameters), |
| io:format("Sending ~p to ~p~n", [UserPrim, ScocPid]), |
| gen_fsm:send_event(ScocPid, UserPrim), |
| LoopDat1; |
| % T(ias) expired on the other end of the connection |
| %#sccp_msg{msg_type = ?SCCP_MSGT_IT} -> |
| _ -> |
| IsConnLess = sccp_codec:is_connectionless(Msg), |
| case IsConnLess of |
| true -> |
| case UserPid of |
| undefined -> |
| io:format("CL message to unequipped SSN~n"); |
| _ -> |
| % it would be more proper to send them via SCLC ?? |
| %gen_fsm:send(sccp_sclc, ?? |
| % FIXME: N-NOTICE.ind for NOTICE |
| UserPrim = osmo_util:make_prim('N','UNITDATA', indication, Msg), |
| UserPid ! {sccp, UserPrim} |
| end; |
| false -> |
| % connection oriented messages need to go via SCOC instance |
| #sccp_msg{parameters = Opts} = Msg, |
| LocalRef = proplists:get_value(dst_local_ref, Opts), |
| ScocPrim = osmo_util:make_prim('RCOC', 'CONNECTION-MSG', indication, Msg), |
| case LocalRef of |
| undefined -> |
| % FIXME: send SCCP_MSGT_ERR |
| io:format("Conn-Msg to undefined ref ~p~n", [Msg]); |
| _ -> |
| tx_prim_to_local_ref(ScocPrim, LocalRef) |
| end |
| end, |
| LoopDat |
| end. |
| |
| |
| % N-CONNECT.req from user: spawn new SCOC and deliver primitive to it |
| idle(P = #primitive{subsystem = 'N', gen_name = 'CONNECT', |
| spec_name = request, parameters = ParamsIn}, LoopDat) -> |
| UserPid = proplists:get_value(user_pid, ParamsIn), |
| ParamsOut = proplists:delete(user_pid, ParamsIn), |
| % Start new SCOC instance |
| {LoopDat1, ScocPid} = spawn_new_scoc(LoopDat, UserPid), |
| % Deliver primitive to new SCOC instance |
| gen_fsm:send_event(ScocPid, P#primitive{parameters = ParamsOut}), |
| {next_state, idle, LoopDat1}; |
| |
| % N-UNITDATA.req from user (normally this is SCLC, but we don't have SCLC) |
| idle(#primitive{subsystem = 'N', gen_name = 'UNITDATA', |
| spec_name = request, parameters = Params}, LoopDat) -> |
| % User needs to specify: Protocol Class, Called Party, Calling Party, Data |
| SccpMsg = #sccp_msg{msg_type = ?SCCP_MSGT_UDT, parameters = Params}, |
| LoopDat2 = send_sccp_local_out(LoopDat, SccpMsg), |
| {next_state, idle, LoopDat2}; |
| |
| % MTP-TRANSFER.ind from lower layer is passed into SCRC |
| idle(#primitive{subsystem = 'MTP', gen_name = 'TRANSFER', |
| spec_name = indication, parameters = Mtp3}, LoopDat) -> |
| case sccp_routing:route_mtp3_sccp_in(Mtp3) of |
| {remote, SccpMsg2, LsName} -> |
| io:format("routed to remote?!?~n"), |
| {ok, M3} = create_mtp3_out(SccpMsg2, LsName), |
| % generate a MTP-TRANSFER.req primitive to the lower layer |
| send_mtp_transfer_down(M3, LsName), |
| LoopDat1 = LoopDat; |
| {local, SccpMsg, UserPid} -> |
| % store the MTP3 routing label in case of CC, as SCCP |
| % needs to know it in order to send CO messages later |
| if SccpMsg#sccp_msg.msg_type == ?SCCP_MSGT_CC; |
| SccpMsg#sccp_msg.msg_type == ?SCCP_MSGT_CR -> |
| Params = SccpMsg#sccp_msg.parameters, |
| Mtp3Label = Mtp3#mtp3_msg.routing_label, |
| ParamsNew = [{mtp3_label, Mtp3Label}], |
| SccpMsg2 = SccpMsg#sccp_msg{parameters = Params ++ ParamsNew}; |
| true -> |
| SccpMsg2 = SccpMsg |
| end, |
| LoopDat1 = deliver_to_scoc_sclc(LoopDat, SccpMsg2, UserPid); |
| {error, Reason} -> |
| io:format("route_mtp3_sccp_in: Error ~w~n", [Reason]), |
| LoopDat1 = LoopDat |
| end, |
| {next_state, idle, LoopDat1}; |
| idle({sclc_scrc_connless_msg, SccpMsg}, LoopDat) -> |
| % FIXME: see above, N-UNITDATA.req from user |
| {next_state, idle, LoopDat}; |
| % connection oriented messages like N-DATA.req from user |
| idle(#primitive{subsystem = 'OCRC', gen_name = 'CONNECTION-MSG', |
| spec_name = request, parameters = [SccpMsg, Label]}, LoopDat) -> |
| % use the label to route, not the SCCP header!! |
| % according to (2) of sheet 5 SCRC state machine Q.714 |
| SccpEnc = sccp_codec:encode_sccp_msg(SccpMsg), |
| M3 = #mtp3_msg{network_ind = ?MTP3_NETIND_INTERNATIONAL, |
| service_ind = ?MTP3_SERV_SCCP, |
| routing_label = Label, |
| payload = SccpEnc}, |
| case ss7_routes:route_dpc(Label#mtp3_routing_label.dest_pc) of |
| {ok, LsName} -> |
| send_mtp_transfer_down(M3, LsName); |
| {error, Error} -> |
| io:format("unable to find linkset fo Dpc ~p CONNECTION-MSG~n", |
| [Label#mtp3_routing_label.dest_pc]) |
| end, |
| {next_state, idle, LoopDat}; |
| % SCOC has received confirmation about new incoming connection from user |
| idle(#primitive{subsystem = 'OCRC', gen_name = 'CONNECTION', |
| spec_name = confirm, parameters = Params}, LoopDat) -> |
| SccpMsg = #sccp_msg{msg_type=?SCCP_MSGT_CC, parameters=Params}, |
| LoopDat2 = send_sccp_local_out(LoopDat, SccpMsg), |
| {next_state, idle, LoopDat2}; |
| |
| |
| % triggered by N-CONNECT.req from user to SCOC: |
| idle(#primitive{subsystem = 'OCRC', gen_name = 'CONNECTION', |
| spec_name = indication, parameters = Params}, LoopDat) -> |
| SccpMsg = #sccp_msg{msg_type=?SCCP_MSGT_CR, parameters=Params}, |
| LoopDat2 = send_sccp_local_out(LoopDat, SccpMsg), |
| {next_state, idle, LoopDat2}. |
| |
| send_mtp_transfer_down(Mtp3) when is_record(Mtp3, mtp3_msg) -> |
| ss7_links:mtp3_tx(Mtp3). |
| |
| send_mtp_transfer_down(Mtp3, LsName) when is_record(Mtp3, mtp3_msg) -> |
| ss7_links:mtp3_tx(Mtp3, LsName). |
| |
| create_mtp3_out(SccpMsg, LsName) when is_record(SccpMsg, sccp_msg) -> |
| CalledParty = proplists:get_value(called_party_addr, |
| SccpMsg#sccp_msg.parameters), |
| % we _have_ to have a destination point code here |
| Dpc = CalledParty#sccp_addr.point_code, |
| case Dpc of |
| undefined -> |
| {error, dpc_undefined}; |
| _ -> |
| Opc = sccp_routing:select_opc(SccpMsg, LsName), |
| case Opc of |
| undefined -> |
| {error, opc_undefined}; |
| _ -> |
| % FIXME: implement XUDT / LUDT support |
| SccpEnc = sccp_codec:encode_sccp_msg(SccpMsg), |
| % FIXME: select sls at random |
| M3R = #mtp3_routing_label{sig_link_sel = 0, |
| origin_pc = Opc, |
| dest_pc = Dpc}, |
| M3 = #mtp3_msg{network_ind = ?MTP3_NETIND_INTERNATIONAL, |
| service_ind = ?MTP3_SERV_SCCP, |
| routing_label = M3R, |
| payload = SccpEnc}, |
| {ok, M3} |
| end |
| end. |
| |
| send_sccp_local_out(LoopDat, SccpMsg) when is_record(SccpMsg, sccp_msg) -> |
| case sccp_routing:route_local_out(SccpMsg) of |
| {remote, SccpMsg2, LsName} -> |
| % FIXME: get to MTP-TRANSFER.req |
| {ok, M3} = create_mtp3_out(SccpMsg2, LsName), |
| % generate a MTP-TRANSFER.req primitive to the lower layer |
| send_mtp_transfer_down(M3, LsName), |
| LoopDat; |
| {local, SccpMsg2, UserPid} -> |
| deliver_to_scoc_sclc(LoopDat, SccpMsg2, UserPid); |
| {error, Reason} -> |
| io:format("sccp_local_out Routing Failure ~p~n", [SccpMsg]), |
| LoopDat |
| end. |
| |
| % FIXME: the MTP3 code should net send a gen_serve:cast ?!? |
| handle_info({'$gen_cast', P=#primitive{}}, _State, LoopDat) -> |
| #primitive{subsystem = 'MTP', gen_name = 'TRANSFER', |
| spec_name = indication, parameters = Mtp3} = P, |
| gen_fsm:send_event(self(), P), |
| {next_state, idle, LoopDat}. |