| module MSC_ConnectionHandler { |
| |
| /* (C) 2017-2019 Harald Welte <laforge@gnumonks.org> |
| * All rights reserved. |
| * |
| * Released under the terms of GNU General Public License, Version 2 or |
| * (at your option) any later version. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| import from General_Types all; |
| import from Osmocom_Types all; |
| import from SCCPasp_Types all; |
| import from BSSAP_Types all; |
| import from BSSAP_CodecPort all; |
| import from RAN_Emulation all; |
| import from BSSMAP_Templates all; |
| |
| import from MGCP_Types all; |
| import from MGCP_Templates all; |
| import from SDP_Types all; |
| |
| type function void_fn_msc(charstring id) runs on MSC_ConnHdlr; |
| |
| /* this component represents a single subscriber connection at the MSC. |
| * There is a 1:1 mapping between SCCP connections and RAN_ConnHdlr components. |
| * We inherit all component variables, ports, functions, ... from RAN_ConnHdlr */ |
| type component MSC_ConnHdlr extends RAN_ConnHdlr { |
| /* SCCP Connecction Identifier for the underlying SCCP connection */ |
| var integer g_sccp_conn_id; |
| |
| var MSC_State g_state := MSC_STATE_NONE; |
| var MgcpEndpoint g_ep_name; |
| var MgcpCallId g_call_id; |
| var MgcpConnectionId g_mgcp_conn_id; |
| var MSC_TestHdlrParams g_pars; |
| } |
| |
| type record MSC_TestHdlrParams { |
| integer connection_id, |
| integer e1_timeslot |
| }; |
| |
| /* helper function to create and connect a MSC_ConnHdlr component */ |
| private function f_connect_handler(inout MSC_ConnHdlr vc_conn) runs on RAN_Emulation_CT { |
| /* connect client BSSAP port to BSSAP dispatcher */ |
| connect(vc_conn:BSSAP, self:CLIENT); |
| } |
| |
| private function f_msc_start_handler(void_fn_msc fn, charstring id, template (omit) MSC_TestHdlrParams pars := omit) |
| runs on RAN_Emulation_CT return MSC_ConnHdlr { |
| var MSC_ConnHdlr vc_conn; |
| vc_conn := MSC_ConnHdlr.create(id); |
| f_connect_handler(vc_conn); |
| vc_conn.start(f_handler_init(fn, id, pars)); |
| return vc_conn; |
| } |
| |
| /* first function inside ConnHdlr component; sets g_pars + starts function */ |
| private function f_handler_init(void_fn_msc fn, charstring id, template (omit) MSC_TestHdlrParams pars := omit) |
| runs on MSC_ConnHdlr { |
| if (isvalue(pars)) { |
| g_pars := valueof(pars); |
| } |
| fn.apply(id); |
| } |
| |
| /* Callback function from general BSSMAP_Emulation whenever a connectionless |
| * BSSMAP message arrives. Can retunr a PDU_BSSAP that should be sent in return */ |
| private function UnitdataCallback(PDU_BSSAP bssap) |
| runs on RAN_Emulation_CT return template PDU_BSSAP { |
| var template PDU_BSSAP resp := omit; |
| |
| if (match(bssap, tr_BSSMAP_Reset)) { |
| resp := ts_BSSMAP_ResetAck; |
| } |
| |
| return resp; |
| } |
| |
| /* Callback function from general BSSMAP_Emulation whenever a new incoming |
| * SCCP connection arrivces. Must create + start a new component */ |
| private function CreateCallback_generic(void_fn_msc fn, BSSAP_N_CONNECT_ind conn_ind, charstring id) |
| runs on RAN_Emulation_CT return RAN_ConnHdlr { |
| var MSC_ConnHdlr vc_conn; |
| var MSC_TestHdlrParams pars; |
| pars.connection_id := conn_ind.connectionId; |
| pars.e1_timeslot := g_next_e1_ts; |
| |
| /* Create a new RAN_ConnHdlr component */ |
| vc_conn := f_msc_start_handler(fn, g_ran_id & "-Conn-" & int2str(conn_ind.connectionId), pars); |
| |
| /* increment next E1 timeslot */ |
| g_next_e1_ts := g_next_e1_ts + 1; |
| return vc_conn; |
| } |
| |
| const RanOps MSC_RanOps := { |
| create_cb := omit, |
| unitdata_cb := refers(UnitdataCallback), |
| decode_dtap := false, |
| role_ms := false, |
| protocol := RAN_PROTOCOL_BSSAP, |
| transport := BSSAP_TRANSPORT_AoIP, |
| use_osmux := false, |
| sccp_addr_local := omit, |
| sccp_addr_peer := omit |
| } |
| |
| type enumerated MSC_State { |
| MSC_STATE_NONE, |
| MSC_STATE_WAIT_ASS_COMPL, |
| MSC_STATE_WAIT_CRCX_ACK, |
| MSC_STATE_WAIT_MDCX_ACK, |
| MSC_STATE_WAIT_CLEAR_COMPL, |
| MSC_STATE_WAIT_DLCX_ACK |
| } |
| |
| /* Callback function from general BSSMAP_Emulation whenever a new incoming |
| * SCCP connection arrivces. Must create + start a new component */ |
| function CreateCallback_establish_fully(BSSAP_N_CONNECT_ind conn_ind, charstring id) |
| runs on RAN_Emulation_CT return RAN_ConnHdlr { |
| return CreateCallback_generic(refers(f_msc_establish_fully), conn_ind, id); |
| } |
| /* main function processing various incoming events */ |
| function f_msc_establish_fully(charstring id) runs on MSC_ConnHdlr { |
| var MgcpResponse mgcp_rsp; |
| timer T := 5.0; |
| |
| g_sccp_conn_id := g_pars.connection_id; |
| g_call_id := f_mgcp_alloc_call_id(); |
| g_ep_name := hex2str(int2hex(g_pars.e1_timeslot, 1)) & "@mgw"; |
| |
| /* we just accepted an incoming SCCP connection, start guard timer */ |
| T.start; |
| |
| while (true) { |
| var PDU_BSSAP bssap; |
| alt { |
| /* new SCCP-level connection indication from BSC */ |
| [g_state == MSC_STATE_NONE] BSSAP.receive(tr_BSSMAP_ComplL3) -> value bssap { |
| /* respond with ASSIGNMENT CMD */ |
| g_state := MSC_STATE_WAIT_ASS_COMPL; |
| BSSAP.send(ts_BSSMAP_AssignmentReq(ts_BSSMAP_IE_CIC(0, g_pars.e1_timeslot))); |
| } |
| [g_state == MSC_STATE_WAIT_ASS_COMPL] BSSAP.receive(tr_BSSMAP_AssignmentComplete(?,*)) { |
| /* FIXME: Send MGCP CRCX */ |
| g_state := MSC_STATE_WAIT_CRCX_ACK; |
| var MgcpTransId trans_id := f_mgcp_alloc_tid(); |
| //template SDP_Message sdp := omit; |
| BSSAP.send(ts_CRCX(trans_id, g_ep_name, "recvonly", g_call_id)); //, sdp)); |
| } |
| /* |
| [] BSSAP.receive(tr_BSSMAP_AssignmentFail) { |
| } |
| */ |
| |
| /* receive CRCX ACK: transmit MDCX */ |
| [g_state == MSC_STATE_WAIT_CRCX_ACK] BSSAP.receive(tr_CRCX_ACK) -> value mgcp_rsp { |
| /* extract connection ID */ |
| g_mgcp_conn_id := f_MgcpResp_extract_conn_id(mgcp_rsp); |
| g_state := MSC_STATE_WAIT_MDCX_ACK; |
| var MgcpTransId trans_id := f_mgcp_alloc_tid(); |
| BSSAP.send(ts_MDCX(trans_id, g_ep_name, "sendrecv", g_call_id, g_mgcp_conn_id)); |
| } |
| |
| /* receive MDCX ACK: wait + transmit CLEAR COMMAND */ |
| [g_state == MSC_STATE_WAIT_MDCX_ACK] BSSAP.receive(tr_CRCX_ACK) -> value mgcp_rsp { |
| g_state := MSC_STATE_WAIT_CLEAR_COMPL |
| BSSAP.send(ts_BSSMAP_ClearCommand(9)); /* Cause: call control */ |
| } |
| |
| /* CLEAR COMPLETE from BSS (response to CLEAR COMMAND) */ |
| [g_state == MSC_STATE_WAIT_CLEAR_COMPL] BSSAP.receive(tr_BSSMAP_ClearComplete) { |
| /* send DLCX */ |
| g_state := MSC_STATE_WAIT_DLCX_ACK; |
| var MgcpTransId trans_id := f_mgcp_alloc_tid(); |
| BSSAP.send(ts_DLCX(trans_id, g_ep_name, g_call_id)); |
| } |
| |
| [g_state == MSC_STATE_WAIT_DLCX_ACK] BSSAP.receive(tr_DLCX_ACK) { |
| BSSAP.send(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_REQ); |
| setverdict(pass); |
| self.stop; |
| } |
| |
| /* TODO: CLEAR REQUEST from BSS */ |
| |
| [] BSSAP.receive(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_IND) { |
| setverdict(fail); |
| self.stop; |
| } |
| |
| [] BSSAP.receive(PDU_BSSAP:?) -> value bssap { |
| log("Received unhandled SCCP-CO: ", bssap); |
| } |
| |
| /* Guard timer has expired, close connection */ |
| [] T.timeout { |
| BSSAP.send(RAN_Conn_Prim:MSC_CONN_PRIM_DISC_REQ); |
| setverdict(fail, "Timeout of guard timer"); |
| self.stop; |
| } |
| |
| } |
| } |
| } |
| |
| } |