503 lines
15 KiB
Plaintext
503 lines
15 KiB
Plaintext
module MGCP_Emulation {
|
|
|
|
/* MGCP Emulation, runs on top of MGCP_CodecPort. It multiplexes/demultiplexes
|
|
* the individual connections, so there can be separate TTCN-3 components handling
|
|
* each of the connections.
|
|
*
|
|
* The MGCP_Emulation.main() function processes MGCP primitives from the MGCP
|
|
* socket via the MGCP_CodecPort, and dispatches them to the per-connection components.
|
|
*
|
|
* For each new inbound connection, the MgcpOps.create_cb() is called. It can create
|
|
* or resolve a TTCN-3 component, and returns a component reference to which that inbound
|
|
* connection is routed/dispatched.
|
|
*
|
|
* If a pre-existing component wants to register to handle a future inbound call, it can
|
|
* do so by registering an "expect" with the expected destination phone number. This is e.g. useful
|
|
* if you are simulating BSC + MGCP, and first trigger a connection from BSC side in a
|
|
* component which then subsequently should also handle the MGCP emulation.
|
|
*
|
|
* Inbound Unit Data messages (such as are dispatched to the MgcpOps.unitdata_cb() callback,
|
|
* which is registered with an argument to the main() function below.
|
|
*
|
|
* (C) 2017-2018 by Harald Welte <laforge@gnumonks.org>
|
|
* (C) 2018 by sysmocom - s.f.m.c. GmbH, Author: Daniel Willmann
|
|
* 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 MGCP_CodecPort all;
|
|
import from MGCP_CodecPort_CtrlFunct all;
|
|
import from MGCP_Types all;
|
|
import from MGCP_Templates all;
|
|
import from Osmocom_Types all;
|
|
import from IPL4asp_Types all;
|
|
|
|
type component MGCP_ConnHdlr {
|
|
/* Simple send/recv without caring about peer addr+port. Used with multi_conn_mode=false. */
|
|
port MGCP_Conn_PT MGCP;
|
|
/* Handle multiple connections concurrently. Used with multi_conn_mode=true. */
|
|
port MGCP_Conn_Multi_PT MGCP_MULTI;
|
|
/* procedure based port to register for incoming connections. */
|
|
port MGCPEM_PROC_PT MGCP_PROC;
|
|
}
|
|
|
|
/* port between individual per-connection components and this dispatcher */
|
|
type port MGCP_Conn_PT message {
|
|
inout MgcpCommand, MgcpResponse;
|
|
} with { extension "internal" };
|
|
|
|
/* port between individual per-connection components and this dispatcher */
|
|
type port MGCP_Conn_Multi_PT message {
|
|
inout MGCP_RecvFrom, MGCP_SendTo;
|
|
} with { extension "internal" };
|
|
|
|
/* represents a single MGCP Endpoint */
|
|
type record EndpointData {
|
|
MGCP_ConnHdlr comp_ref,
|
|
MgcpEndpoint endpoint optional
|
|
};
|
|
|
|
/* pending CRCX with their transaction ID */
|
|
type set of MgcpTransId MgcpTransIds;
|
|
|
|
type component MGCP_Emulation_CT {
|
|
/* Port facing to the UDP SUT */
|
|
port MGCP_CODEC_PT MGCP;
|
|
/* All MGCP_ConnHdlr MGCP ports connect here
|
|
* MGCP_Emulation_CT.main needs to figure out what messages
|
|
* to send where with CLIENT.send() to vc_conn */
|
|
port MGCP_Conn_PT MGCP_CLIENT;
|
|
/* This one is used with multi_conn_mode=true and allows differentiating UDP sockets */
|
|
port MGCP_Conn_Multi_PT MGCP_CLIENT_MULTI;
|
|
/* currently tracked connections */
|
|
var EndpointData MgcpEndpointTable[16];
|
|
var MgcpTransIds MgcpPendingTrans := {};
|
|
/* pending expected CRCX */
|
|
var ExpectData MgcpExpectTable[8];
|
|
/* procedure based port to register for incoming connections */
|
|
port MGCPEM_PROC_PT MGCP_PROC;
|
|
|
|
var charstring g_mgcp_id;
|
|
var integer g_mgcp_conn_id := -1;
|
|
|
|
var MGCP_conn_parameters g_pars;
|
|
}
|
|
|
|
type function MGCPCreateCallback(MgcpCommand cmd, charstring id)
|
|
runs on MGCP_Emulation_CT return MGCP_ConnHdlr;
|
|
|
|
type function MGCPUnitdataCallback(MgcpMessage msg)
|
|
runs on MGCP_Emulation_CT return template MgcpMessage;
|
|
|
|
type record MGCPOps {
|
|
MGCPCreateCallback create_cb,
|
|
MGCPUnitdataCallback unitdata_cb
|
|
}
|
|
|
|
type record MGCP_conn_parameters {
|
|
HostName callagent_ip,
|
|
PortNumber callagent_udp_port,
|
|
HostName mgw_ip,
|
|
PortNumber mgw_udp_port,
|
|
boolean multi_conn_mode
|
|
}
|
|
|
|
function tr_MGCP_RecvFrom_R(template MgcpMessage msg)
|
|
runs on MGCP_Emulation_CT return template MGCP_RecvFrom {
|
|
var template MGCP_RecvFrom mrf := {
|
|
connId := g_mgcp_conn_id,
|
|
remName := ?,
|
|
remPort := ?,
|
|
locName := ?,
|
|
locPort := ?,
|
|
msg := msg
|
|
}
|
|
return mrf;
|
|
}
|
|
|
|
private function f_ep_known(MgcpEndpoint ep)
|
|
runs on MGCP_Emulation_CT return boolean {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (MgcpEndpointTable[i].endpoint == ep) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function f_comp_known(MGCP_ConnHdlr client)
|
|
runs on MGCP_Emulation_CT return boolean {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (MgcpEndpointTable[i].comp_ref == client) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function f_comp_by_ep(MgcpEndpoint ep)
|
|
runs on MGCP_Emulation_CT return MGCP_ConnHdlr {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (MgcpEndpointTable[i].endpoint == ep) {
|
|
return MgcpEndpointTable[i].comp_ref;
|
|
}
|
|
}
|
|
setverdict(fail, "MGCP Endpoint Table not found by Endpoint", ep);
|
|
mtc.stop;
|
|
}
|
|
|
|
private function f_ep_by_comp(MGCP_ConnHdlr client)
|
|
runs on MGCP_Emulation_CT return MgcpEndpoint {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (MgcpEndpointTable[i].comp_ref == client) {
|
|
return MgcpEndpointTable[i].endpoint;
|
|
}
|
|
}
|
|
setverdict(fail, "MGCP Endpoint Table not found by component ", client);
|
|
mtc.stop;
|
|
}
|
|
|
|
private function f_ep_table_add(MGCP_ConnHdlr comp_ref, MgcpEndpoint ep)
|
|
runs on MGCP_Emulation_CT {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (not isvalue(MgcpEndpointTable[i].endpoint)) {
|
|
MgcpEndpointTable[i].endpoint := ep;
|
|
MgcpEndpointTable[i].comp_ref := comp_ref;
|
|
return;
|
|
}
|
|
}
|
|
testcase.stop("MGCP Endpoint Table full!");
|
|
}
|
|
|
|
private function f_ep_table_del(MGCP_ConnHdlr comp_ref, MgcpEndpoint ep)
|
|
runs on MGCP_Emulation_CT {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
if (MgcpEndpointTable[i].comp_ref == comp_ref and
|
|
MgcpEndpointTable[i].endpoint == ep) {
|
|
MgcpEndpointTable[i].endpoint := omit;
|
|
MgcpEndpointTable[i].comp_ref := null;
|
|
return;
|
|
}
|
|
}
|
|
setverdict(fail, "MGCP Endpoint Table: Couldn't find to-be-deleted entry!");
|
|
mtc.stop;
|
|
}
|
|
|
|
|
|
/* Check if the given transaction ID is a pending CRCX. If yes, return true + remove */
|
|
private function f_trans_id_was_pending(MgcpTransId trans_id)
|
|
runs on MGCP_Emulation_CT return boolean {
|
|
for (var integer i := 0; i < lengthof(MgcpPendingTrans); i := i+1) {
|
|
if (MgcpPendingTrans[i] == trans_id) {
|
|
/* Remove from list */
|
|
var MgcpTransIds OldPendingTrans := MgcpPendingTrans;
|
|
MgcpPendingTrans := {}
|
|
for (var integer j := 0; j < lengthof(OldPendingTrans); j := j+1) {
|
|
if (j != i) {
|
|
MgcpPendingTrans := MgcpPendingTrans & {OldPendingTrans[j]};
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* TODO: move this to MGCP_Types? */
|
|
function f_mgcp_ep(MgcpMessage msg) return MgcpEndpoint {
|
|
var MgcpParameterList params;
|
|
var integer i;
|
|
if (ischosen(msg.command)) {
|
|
return msg.command.line.ep;
|
|
} else {
|
|
var MgcpEndpoint ep;
|
|
if (f_mgcp_find_param(msg, "Z", ep) == false) {
|
|
setverdict(fail, "No SpecificEndpointName in MGCP response", msg);
|
|
mtc.stop;
|
|
}
|
|
return ep;
|
|
}
|
|
}
|
|
|
|
private function f_ep_table_init()
|
|
runs on MGCP_Emulation_CT {
|
|
for (var integer i := 0; i < sizeof(MgcpEndpointTable); i := i+1) {
|
|
MgcpEndpointTable[i].comp_ref := null;
|
|
MgcpEndpointTable[i].endpoint := omit;
|
|
}
|
|
}
|
|
|
|
private function f_forward_to_client(MGCP_RecvFrom mrf, MGCP_ConnHdlr vc_conn) runs on MGCP_Emulation_CT {
|
|
if (g_pars.multi_conn_mode) {
|
|
MGCP_CLIENT_MULTI.send(mrf) to vc_conn;
|
|
} else {
|
|
MGCP_CLIENT.send(mrf.msg.command) to vc_conn;
|
|
}
|
|
}
|
|
|
|
function main(MGCPOps ops, MGCP_conn_parameters p, charstring id) runs on MGCP_Emulation_CT {
|
|
var Result res;
|
|
g_pars := p;
|
|
g_mgcp_id := id;
|
|
f_ep_table_init();
|
|
f_expect_table_init();
|
|
|
|
map(self:MGCP, system:MGCP_CODEC_PT);
|
|
if (p.multi_conn_mode or p.callagent_udp_port == -1) {
|
|
res := MGCP_CodecPort_CtrlFunct.f_IPL4_listen(MGCP, p.mgw_ip, p.mgw_udp_port, { udp:={} });
|
|
} else {
|
|
res := MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, p.callagent_ip, p.callagent_udp_port, p.mgw_ip, p.mgw_udp_port, -1, { udp:={} });
|
|
}
|
|
if (not ispresent(res.connId)) {
|
|
setverdict(fail, "Could not connect MGCP socket, check your configuration");
|
|
mtc.stop;
|
|
}
|
|
g_mgcp_conn_id := res.connId;
|
|
|
|
while (true) {
|
|
var MGCP_ConnHdlr vc_conn;
|
|
var ExpectCriteria crit;
|
|
var MGCP_RecvFrom mrf;
|
|
var MGCP_SendTo mst;
|
|
var MgcpMessage msg;
|
|
var MgcpCommand cmd;
|
|
var MgcpResponse resp;
|
|
var MgcpEndpoint ep;
|
|
|
|
alt {
|
|
/* MGCP from client */
|
|
[not p.multi_conn_mode] MGCP_CLIENT.receive(MgcpResponse:?) -> value resp sender vc_conn {
|
|
msg := {
|
|
response := resp
|
|
};
|
|
/* If this is the resposne to a pending CRCX, extract Endpoint and store in table */
|
|
if (f_trans_id_was_pending(resp.line.trans_id)) {
|
|
f_ep_table_add(vc_conn, f_mgcp_ep(msg));
|
|
}
|
|
/* Pass message through */
|
|
/* TODO: check which ConnectionID client has allocated + store in table? */
|
|
MGCP.send(t_MGCP_Send(g_mgcp_conn_id, msg));
|
|
}
|
|
|
|
/* MGCP from client in Multi Conn mode */
|
|
[p.multi_conn_mode] MGCP_CLIENT_MULTI.receive(MGCP_SendTo:?) -> value mst sender vc_conn {
|
|
/* If this is the resposne to a pending CRCX, extract Endpoint and store in table */
|
|
if (f_trans_id_was_pending(mst.msg.response.line.trans_id)) {
|
|
f_ep_table_add(vc_conn, f_mgcp_ep(mst.msg));
|
|
}
|
|
/* Pass message through */
|
|
/* TODO: check which ConnectionID client has allocated + store in table? */
|
|
MGCP.send(mst);
|
|
}
|
|
[] MGCP.receive(tr_MGCP_RecvFrom_R(?)) -> value mrf {
|
|
if (not p.multi_conn_mode and p.callagent_udp_port == -1) {
|
|
/* we aren't yet connected to the remote side
|
|
port, let's fix this. This way upper layers
|
|
can use Send/Recv without caring about UDP
|
|
src/dst addr + port */
|
|
p.callagent_udp_port := mrf.remPort;
|
|
res := MGCP_CodecPort_CtrlFunct.f_IPL4_connect(MGCP, p.callagent_ip, p.callagent_udp_port, p.mgw_ip, p.mgw_udp_port, g_mgcp_conn_id, { udp:={} });
|
|
if (not ispresent(res.connId)) {
|
|
setverdict(fail, "Could not connect MGCP socket, check your configuration");
|
|
mtc.stop;
|
|
}
|
|
}
|
|
if (ischosen(mrf.msg.command)) {
|
|
cmd := mrf.msg.command;
|
|
if (f_ep_known(cmd.line.ep)) {
|
|
vc_conn := f_comp_by_ep(cmd.line.ep);
|
|
f_forward_to_client(mrf, vc_conn);
|
|
} else {
|
|
if (cmd.line.verb == "CRCX") {
|
|
vc_conn := ops.create_cb.apply(cmd, id);
|
|
if (not match(cmd.line.ep, t_MGCP_EP_wildcard)) {
|
|
/* non-wildcard EP, use directly */
|
|
f_ep_table_add(vc_conn, cmd.line.ep);
|
|
} else {
|
|
/* add this transaction to list of pending transactions */
|
|
MgcpPendingTrans := MgcpPendingTrans & {cmd.line.trans_id};
|
|
}
|
|
f_forward_to_client(mrf, vc_conn);
|
|
} else {
|
|
/* connectionless MGCP, i.e. messages without ConnectionId */
|
|
var template MgcpMessage r := ops.unitdata_cb.apply(mrf.msg);
|
|
if (isvalue(r)) {
|
|
MGCP.send(t_MGCP_SendToMrf(mrf, r));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
setverdict(fail, "Received unexpected MGCP response: ", mrf.msg.response);
|
|
mtc.stop;
|
|
}
|
|
}
|
|
[] MGCP_PROC.getcall(MGCPEM_register:{?,?}) -> param(crit, vc_conn) {
|
|
f_create_expect(crit, vc_conn);
|
|
MGCP_PROC.reply(MGCPEM_register:{crit, vc_conn}) to vc_conn;
|
|
}
|
|
[] MGCP_PROC.getcall(MGCPEM_delete_ep:{?,?}) -> param(ep, vc_conn) {
|
|
f_ep_table_del(vc_conn, ep);
|
|
MGCP_PROC.reply(MGCPEM_delete_ep:{ep, vc_conn}) to vc_conn;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/* "Expect" Handling */
|
|
|
|
/* */
|
|
type record ExpectCriteria {
|
|
MgcpConnectionId connid optional,
|
|
MgcpEndpoint endpoint optional,
|
|
MgcpTransId transid optional
|
|
}
|
|
|
|
type record ExpectData {
|
|
ExpectCriteria crit optional,
|
|
MGCP_ConnHdlr vc_conn
|
|
}
|
|
|
|
signature MGCPEM_register(in ExpectCriteria cmd, in MGCP_ConnHdlr hdlr);
|
|
signature MGCPEM_delete_ep(in MgcpEndpoint ep, in MGCP_ConnHdlr hdlr);
|
|
|
|
type port MGCPEM_PROC_PT procedure {
|
|
inout MGCPEM_register, MGCPEM_delete_ep;
|
|
} with { extension "internal" };
|
|
|
|
function f_get_mgcp_by_crit(ExpectCriteria crit)
|
|
return template MgcpCommand {
|
|
var template MgcpCommand ret := {
|
|
line := {
|
|
verb := ?,
|
|
trans_id := ?,
|
|
ep := ?,
|
|
ver := ?
|
|
},
|
|
params := *,
|
|
sdp := *
|
|
}
|
|
if (ispresent(crit.connid)) {
|
|
ret.params := { *, ts_MgcpParConnectionId(crit.connid), * };
|
|
}
|
|
if (ispresent(crit.endpoint)) {
|
|
ret.line.ep := crit.endpoint;
|
|
}
|
|
if (ispresent(crit.transid)) {
|
|
ret.line.trans_id := crit.transid;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Function that can be used as create_cb and will usse the expect table */
|
|
function ExpectedCreateCallback(MgcpCommand cmd, charstring id)
|
|
runs on MGCP_Emulation_CT return MGCP_ConnHdlr {
|
|
var MGCP_ConnHdlr ret := null;
|
|
var template MgcpCommand mgcpcmd;
|
|
var integer i;
|
|
|
|
for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
|
|
if (not ispresent(MgcpExpectTable[i].crit)) {
|
|
continue;
|
|
}
|
|
/* FIXME: Ignore criteria for now */
|
|
mgcpcmd := f_get_mgcp_by_crit(MgcpExpectTable[i].crit);
|
|
if (match(cmd, mgcpcmd)) {
|
|
ret := MgcpExpectTable[i].vc_conn;
|
|
/* Release this entry */
|
|
MgcpExpectTable[i].crit := omit;
|
|
MgcpExpectTable[i].vc_conn := null;
|
|
log("Found Expect[", i, "] for ", cmd, " handled at ", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
setverdict(fail, "Couldn't find Expect for CRCX", cmd);
|
|
mtc.stop;
|
|
}
|
|
|
|
private function f_create_expect(ExpectCriteria crit, MGCP_ConnHdlr hdlr)
|
|
runs on MGCP_Emulation_CT {
|
|
var integer i;
|
|
|
|
/* Check an entry like this is not already presnt */
|
|
for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
|
|
if (crit == MgcpExpectTable[i].crit) {
|
|
setverdict(fail, "Crit already present", crit);
|
|
mtc.stop;
|
|
}
|
|
}
|
|
for (i := 0; i < sizeof(MgcpExpectTable); i := i+1) {
|
|
if (not ispresent(MgcpExpectTable[i].crit)) {
|
|
MgcpExpectTable[i].crit := crit;
|
|
MgcpExpectTable[i].vc_conn := hdlr;
|
|
log("Created Expect[", i, "] for ", crit, " to be handled at ", hdlr);
|
|
return;
|
|
}
|
|
}
|
|
testcase.stop("No space left in MgcpExpectTable")
|
|
}
|
|
|
|
/* client/conn_hdlr side function to use procedure port to create expect in emulation */
|
|
function f_create_mgcp_expect(ExpectCriteria dest_number) runs on MGCP_ConnHdlr {
|
|
MGCP_PROC.call(MGCPEM_register:{dest_number, self}) {
|
|
[] MGCP_PROC.getreply(MGCPEM_register:{?,?}) {};
|
|
}
|
|
}
|
|
|
|
/* client/conn_hdlr side function to use procedure port to create expect in emulation */
|
|
function f_create_mgcp_delete_ep(MgcpEndpoint ep) runs on MGCP_ConnHdlr {
|
|
MGCP_PROC.call(MGCPEM_delete_ep:{ep, self}) {
|
|
[] MGCP_PROC.getreply(MGCPEM_delete_ep:{?,?}) {};
|
|
}
|
|
}
|
|
|
|
|
|
private function f_expect_table_init()
|
|
runs on MGCP_Emulation_CT {
|
|
var integer i;
|
|
for (i := 0; i < sizeof(MgcpExpectTable); i := i + 1) {
|
|
MgcpExpectTable[i].crit := omit;
|
|
}
|
|
}
|
|
|
|
function DummyUnitdataCallback(MgcpMessage msg)
|
|
runs on MGCP_Emulation_CT return template MgcpMessage {
|
|
log("Ignoring MGCP ", msg);
|
|
return omit;
|
|
}
|
|
|
|
/* Determine encoding name for a specified payload type number */
|
|
function f_encoding_name_from_pt(SDP_FIELD_PayloadType pt) return charstring {
|
|
if (pt == PT_PCMU) {
|
|
return "PCMU";
|
|
} else if (pt == PT_GSM) {
|
|
return "GSM";
|
|
} else if (pt == PT_PCMA) {
|
|
return "PCMA";
|
|
} else if (pt == PT_GSMEFR) {
|
|
return "GSM-EFR";
|
|
} else if (pt == PT_GSMHR) {
|
|
return "GSM-HR-08";
|
|
} else if (pt == PT_AMR) {
|
|
return "AMR";
|
|
} else if (pt == PT_AMRWB) {
|
|
return "AMR-WB";
|
|
}
|
|
|
|
setverdict(fail, "Unknown payload type ", pt);
|
|
mtc.stop;
|
|
}
|
|
|
|
}
|