module IPA_Emulation { /* This module implements the IPA multiplex protocol on top of TCP, using the IPL4asp * test-port as provider. It implements both client and server roles, as well was the CCM * handshake for establishing the identity of the client to the server. It already knows * certain well-known sub-protocols such as A-bis RSL, MGCP and SCCP and transcodes messages * so the user can work with abstract data types rather than binary messages. It handles * multiple packets inside one TCP segment */ import from IPA_Types all; import from IPA_CodecPort all; import from IPA_CodecPort_CtrlFunct all; import from IPL4asp_Types all; import from IPL4asp_PortType all; import from MTP3asp_Types all; import from MTP3asp_PortType all; import from RSL_Types all; import from MGCP_Types all; import from Osmocom_CTRL_Types all; modulepar { /* Use Osmocom extended IPA mux header */ boolean mp_ipa_mgcp_uses_osmo_ext := true; } type enumerated IpaMode { IPA_MODE_CLIENT, IPA_MODE_SERVER } type record ASP_IPA_Unitdata { IpaStreamId streamId, IpaExtStreamId streamIdExt optional, octetstring payload } type enumerated ASP_IPA_EventUpDown { ASP_IPA_EVENT_DOWN, ASP_IPA_EVENT_UP, ASP_IPA_EVENT_ID_ACK } /* an event indicating us whether or not a connection is physically up or down, * and whether we have received an ID_ACK */ type union ASP_IPA_Event { ASP_IPA_EventUpDown up_down } template ASP_IPA_Event t_ASP_IPA_EVT_UD(ASP_IPA_EventUpDown ud) := { up_down := ud } template ASP_IPA_Unitdata t_ASP_IPA_UD(IpaStreamId sid, octetstring pl, template IpaExtStreamId esid := omit) := { streamId := sid, streamIdExt := esid, payload := pl } /* like ASP_IPA_Unitdata, but with RSL_Message abstract type instead of octetstring */ type record ASP_RSL_Unitdata { IpaStreamId streamId, RSL_Message rsl }; template ASP_RSL_Unitdata ts_ASP_RSL_UD(IpaStreamId sid, template RSL_Message rsl) := { streamId := sid, rsl := valueof(rsl) } template ASP_RSL_Unitdata tr_ASP_RSL_UD(IpaStreamId sid, template RSL_Message rsl) := { streamId := sid, rsl := rsl } template IpaStreamId t_IpaSidRSL := ( IPAC_PROTO_RSL_TRX0, IPAC_PROTO_RSL_TRX1, IPAC_PROTO_RSL_TRX2, IPAC_PROTO_RSL_TRX3 ); /* Client port for general IPA messages, not further decoded */ type port IPA_SP_PT message { inout ASP_IPA_Unitdata, ASP_IPA_Event; } with { extension "internal" } /* Client port for MGCP inside IPA */ type port IPA_MGCP_PT message { inout MgcpCommand, MgcpResponse; } with { extension "internal" } /* Client port for A-bis RSL inside IPA */ type port IPA_RSL_PT message { inout ASP_RSL_Unitdata, ASP_IPA_Event; } with { extension "internal" } /* Client port for CTRL inside IPA */ type port IPA_CTRL_PT message { inout CtrlMessage, ASP_IPA_Event; } with { extension "internal" } type component IPA_Emulation_CT { /* down-facing port to IPA codec port */ port IPA_CODEC_PT IPA_PORT; /* up-facing port to SCCP */ port MTP3asp_SP_PT MTP3_SP_PORT; /* up-facing port for MGCP */ port IPA_MGCP_PT IPA_MGCP_PORT; /* up-facing port for RSL */ port IPA_RSL_PT IPA_RSL_PORT; /* up-facing port for CTRL */ port IPA_CTRL_PT IPA_CTRL_PORT; /* up-facing port for other streams */ port IPA_SP_PT IPA_SP_PORT; var boolean g_initialized := false; var ConnectionId g_ipa_conn_id := -1; /* Are we a BSC/MGW (truel) or MSC (false) */ var boolean g_is_bsc_mgw; var IpaMode g_mode; var IPA_CCM_Parameters g_ccm_pars := c_IPA_default_ccm_pars; } type record IPA_CCM_Parameters { charstring ser_nr optional, charstring name optional, charstring location1 optional, charstring location2 optional, charstring equip_version optional, charstring sw_version optional, charstring ip_addr optional, charstring mac_addr optional, charstring unit_id optional, charstring osmo_rand optional } const IPA_CCM_Parameters c_IPA_default_ccm_pars := { ser_nr := "", name := "mahlzeit", location1 := "", location2 := "", equip_version := "", sw_version := "", ip_addr := "", mac_addr := "", unit_id := "0/1/2", osmo_rand := "" }; /* Function to use to connect as client to a remote IPA Server */ function f_connect(charstring remote_host, PortNumber remote_port, charstring local_host, PortNumber local_port, IPA_CCM_Parameters ccm_pars := c_IPA_default_ccm_pars) runs on IPA_Emulation_CT { var Result res; res := IPA_CodecPort_CtrlFunct.f_IPL4_connect(IPA_PORT, remote_host, remote_port, local_host, local_port, 0, { tcp:={} }); g_ipa_conn_id := res.connId; g_ccm_pars := ccm_pars; g_is_bsc_mgw := true; } /* Function to use to bind to a local port as IPA server, accepting remote clients */ function f_bind(charstring local_host, PortNumber local_port, IPA_CCM_Parameters ccm_pars := c_IPA_default_ccm_pars) runs on IPA_Emulation_CT { var Result res; res := IPA_CodecPort_CtrlFunct.f_IPL4_listen(IPA_PORT, local_host, local_port, { tcp:={} }); g_ipa_conn_id := res.connId; g_ccm_pars := ccm_pars; g_is_bsc_mgw := false; } template ASP_MTP3_TRANSFERind ts_MTP3_XFER_ind(integer opc, octetstring data) := { sio := { '10'B, '00'B, '0011'B }, opc := opc, dpc := 0, sls := 0, data := data } private template IpaCcmRespPart t_IdRespPart(IpaCcmIdTag tag, charstring payload) := { len := 0, /* overwritten by codec */ tag := tag, data := payload } private function f_send_IPA_EVT(template ASP_IPA_Event evt) runs on IPA_Emulation_CT { if (IPA_RSL_PORT.checkstate("Connected")) { IPA_RSL_PORT.send(evt); } if (IPA_CTRL_PORT.checkstate("Connected")) { IPA_CTRL_PORT.send(evt); } /* FIXME: to other ports */ } /* build IPA CCM ID RESP response from IPA CCM GET */ private function f_ccm_make_id_resp(PDU_IPA_CCM get) runs on IPA_Emulation_CT return PDU_IPA_CCM { var integer i; var PDU_IPA_CCM resp := { msg_type := IPAC_MSGT_ID_RESP, u := { resp := {} } } for (i := 0; i < sizeof(get.u.get); i := i + 1) { var IpaCcmIdTag tag := get.u.get[i].tag; var charstring foo; select (tag) { case (IPAC_IDTAG_SERNR) { foo := g_ccm_pars.ser_nr; } case (IPAC_IDTAG_UNITNAME) { foo := g_ccm_pars.name; } case (IPAC_IDTAG_LOCATION1) { foo := g_ccm_pars.location1; } case (IPAC_IDTAG_LOCATION2) { foo := g_ccm_pars.location2; } case (IPAC_IDTAG_EQUIPVERS) { foo := g_ccm_pars.equip_version; } case (IPAC_IDTAG_SWVERSION) { foo := g_ccm_pars.sw_version; } case (IPAC_IDTAG_IPADDR) { foo := g_ccm_pars.ip_addr; } case (IPAC_IDTAG_MACADDR) { foo := g_ccm_pars.mac_addr; } case (IPAC_IDTAG_UNIT) { foo := g_ccm_pars.unit_id; } case (IPAC_IDTAG_OSMO_RAND) { foo := g_ccm_pars.osmo_rand; } case else { foo := "unknown"; } } resp.u.resp[sizeof(resp.u.resp)] := valueof(t_IdRespPart(tag, foo)); } return resp; } /* transmit IPA CCM message */ private function f_ccm_tx(PDU_IPA_CCM ccm) runs on IPA_Emulation_CT { var IPA_Send ipa_tx := valueof(t_IPA_Send(g_ipa_conn_id, IPAC_PROTO_CCM, enc_PDU_IPA_CCM(ccm))); log("CCM Tx:", ccm); IPA_PORT.send(ipa_tx); } template PDU_IPA_CCM ts_IPA_PONG := { msg_type := IPAC_MSGT_PONG, u := omit } template PDU_IPA_CCM ts_IPA_ACK := { msg_type := IPAC_MSGT_ID_ACK, u := omit } template PDU_IPA_CCM ts_IPA_ID_GET := { msg_type := IPAC_MSGT_ID_GET, u := { get := { { 1, IPAC_IDTAG_UNITNAME } } } } /* receive IPA CCM message */ private function f_ccm_rx(PDU_IPA_CCM ccm) runs on IPA_Emulation_CT { select (ccm.msg_type) { case (IPAC_MSGT_PING) { f_ccm_tx(valueof(ts_IPA_PONG)); } case (IPAC_MSGT_ID_ACK) { f_ccm_tx(valueof(ts_IPA_ACK)); } case (IPAC_MSGT_ID_GET) { f_ccm_tx(f_ccm_make_id_resp(ccm)); } case else { log("Unknown/unsupported IPA CCM message type", ccm); } } } private function f_to_asp(IPA_RecvFrom ipa_rx) return ASP_IPA_Unitdata { var ASP_IPA_Unitdata ret := { streamId := ipa_rx.streamId, streamIdExt := ipa_rx.streamIdExt, payload := ipa_rx.msg } return ret; } private function f_from_asp(ConnectionId connId, ASP_IPA_Unitdata ipa_tx) return IPA_Send { var IPA_Send ret := valueof(t_IPA_Send(connId, ipa_tx.streamId, ipa_tx.payload, ipa_tx.streamIdExt)); return ret; } private function f_from_rsl(ConnectionId connId, ASP_RSL_Unitdata rsl_tx) return IPA_Send { var octetstring payload := enc_RSL_Message(rsl_tx.rsl); var IPA_Send ret := valueof(t_IPA_Send(connId, rsl_tx.streamId, payload)); return ret; } /* main function to use for a client-side IPA implementation */ function main_client(charstring remote_host, PortNumber remote_port, charstring local_host, PortNumber local_port, IPA_CCM_Parameters ccm_pars := c_IPA_default_ccm_pars) runs on IPA_Emulation_CT { g_mode := IPA_MODE_CLIENT; f_connect(remote_host, remote_port, local_host, local_port, ccm_pars); f_send_IPA_EVT(t_ASP_IPA_EVT_UD(ASP_IPA_EVENT_UP)); ScanEvents(); } /* main function to use for a server-side IPA implementation */ function main_server(charstring local_host, PortNumber local_port) runs on IPA_Emulation_CT { g_mode := IPA_MODE_SERVER; f_bind(local_host, local_port); ScanEvents(); } private function f_mgcp_to_user(octetstring msg) runs on IPA_Emulation_CT { var charstring msg_ch := oct2char(msg); if (g_is_bsc_mgw) { log("============"); log(msg_ch); IPA_MGCP_PORT.send(dec_MgcpCommand(msg_ch)); } else { IPA_MGCP_PORT.send(dec_MgcpResponse(msg_ch)); } } private function f_ctrl_to_user(octetstring msg) runs on IPA_Emulation_CT { var charstring msg_ch := oct2char(msg); IPA_CTRL_PORT.send(dec_CtrlMessage(msg_ch)); } private function f_mgcp_to_ud(octetstring payload) runs on IPA_Emulation_CT return ASP_IPA_Unitdata { if (mp_ipa_mgcp_uses_osmo_ext) { return valueof(t_ASP_IPA_UD(IPAC_PROTO_MGCP_OLD, payload)); } else { return valueof(t_ASP_IPA_UD(IPAC_PROTO_OSMO, payload, IPAC_PROTO_EXT_MGCP)); } } /* main loop function for both client and server. 'thread' of the component */ private function ScanEvents() runs on IPA_Emulation_CT { var IPA_RecvFrom ipa_rx; var ASP_IPA_Unitdata ipa_ud; var ASP_MTP3_TRANSFERreq mtp_req; var ASP_Event asp_evt; var MgcpCommand mgcp_cmd; var MgcpResponse mgcp_rsp; var CtrlMessage ctrl_msg; var octetstring payload; var ASP_RSL_Unitdata rsl; /* Set function for dissecting the binary */ var f_IPL4_getMsgLen vl_f := refers(f_IPL4_fixedMsgLen); IPA_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(IPA_PORT, g_ipa_conn_id, vl_f, {0, 2, 3, 1, 0}); while (true) { alt { /* Received IPA -> up into SCCP stack */ [] IPA_PORT.receive(IPA_RecvFrom: ?) -> value ipa_rx { select (ipa_rx.streamId) { case (IPAC_PROTO_CCM) { var PDU_IPA_CCM ccm := dec_PDU_IPA_CCM(ipa_rx.msg); log("CCM Rx:", ccm); f_ccm_rx(ccm); } case (IPAC_PROTO_SCCP) { var ASP_MTP3_TRANSFERind mtp; mtp := valueof(ts_MTP3_XFER_ind(0, ipa_rx.msg)); MTP3_SP_PORT.send(mtp); } case (IPAC_PROTO_MGCP_OLD) { f_mgcp_to_user(ipa_rx.msg); } case (t_IpaSidRSL) { rsl := { streamId := ipa_rx.streamId, rsl := dec_RSL_Message(ipa_rx.msg) }; IPA_RSL_PORT.send(rsl); } case (IPAC_PROTO_OSMO) { select (ipa_rx.streamIdExt) { case (IPAC_PROTO_EXT_MGCP) { f_mgcp_to_user(ipa_rx.msg); } case (IPAC_PROTO_EXT_CTRL) { f_ctrl_to_user(ipa_rx.msg); } case else { IPA_SP_PORT.send(f_to_asp(ipa_rx)); } } } case else { IPA_SP_PORT.send(f_to_asp(ipa_rx)); } } } /* server only */ [] IPA_PORT.receive(ASP_Event:{connOpened:=?}) -> value asp_evt { log("IPA: Connected"); g_ipa_conn_id := asp_evt.connOpened.connId; f_send_IPA_EVT(t_ASP_IPA_EVT_UD(ASP_IPA_EVENT_UP)); if (g_mode == IPA_MODE_SERVER) { f_ccm_tx(valueof(ts_IPA_ID_GET)); } } [] IPA_PORT.receive(ASP_Event:{connClosed:=?}) -> value asp_evt { log("IPA: Closed"); g_ipa_conn_id := -1; f_send_IPA_EVT(t_ASP_IPA_EVT_UD(ASP_IPA_EVENT_DOWN)); self.stop; } /* Received SCCP -> down into IPA */ [] MTP3_SP_PORT.receive(ASP_MTP3_TRANSFERreq: ?) -> value mtp_req { var IPA_Send ipa_tx := valueof(t_IPA_Send(g_ipa_conn_id, IPAC_PROTO_SCCP, mtp_req.data)); IPA_PORT.send(ipa_tx); } /* Received MGCP -> down into IPA */ [] IPA_MGCP_PORT.receive(MgcpCommand:?) -> value mgcp_cmd { payload := char2oct(enc_MgcpCommand(mgcp_cmd)); ipa_ud := f_mgcp_to_ud(payload); IPA_PORT.send(f_from_asp(g_ipa_conn_id, ipa_ud)); } [] IPA_MGCP_PORT.receive(MgcpResponse:?) -> value mgcp_rsp { payload := char2oct(enc_MgcpResponse(mgcp_rsp)); ipa_ud := f_mgcp_to_ud(payload); IPA_PORT.send(f_from_asp(g_ipa_conn_id, ipa_ud)); } [] IPA_CTRL_PORT.receive(CtrlMessage:?) -> value ctrl_msg { payload := char2oct(enc_CtrlMessage(ctrl_msg)); ipa_ud := valueof(t_ASP_IPA_UD(IPAC_PROTO_OSMO, payload, IPAC_PROTO_EXT_CTRL)); IPA_PORT.send(f_from_asp(g_ipa_conn_id, ipa_ud)); } /* Received RSL -> down into IPA */ [] IPA_RSL_PORT.receive(ASP_RSL_Unitdata:?) -> value rsl { IPA_PORT.send(f_from_rsl(g_ipa_conn_id, rsl)); } /* Received MISC (OML/CTRL) -> down into IPA */ [] IPA_SP_PORT.receive(ASP_IPA_Unitdata: ?) -> value ipa_ud { IPA_PORT.send(f_from_asp(g_ipa_conn_id, ipa_ud)); } } } } }