blob: 5bd4e508b20a45d218b1511d1bcc7e64919a6042 [file] [log] [blame]
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.
*/
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 {
port MGCP_Conn_PT MGCP;
/* procedure based port to register for incoming connections */
port MGCPEM_PROC_PT MGCP_PROC;
var MgcpConnectionId mgcp_conn_id;
}
/* port between individual per-connection components and this dispatcher */
type port MGCP_Conn_PT message {
inout MgcpCommand, MgcpResponse;
} with { extension "internal" };
/* represents a single MGCP Connection */
type record ConnectionData {
MGCP_ConnHdlr comp_ref,
MgcpConnectionId conn_id optional
};
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;
/* currently tracked connections */
var ConnectionData MgcpConnectionTable[16];
/* 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;
}
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
}
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_conn_id_known(MgcpConnectionId conn_id)
runs on MGCP_Emulation_CT return boolean {
var integer i;
for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
if (MgcpConnectionTable[i].conn_id == conn_id) {
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(MgcpConnectionTable); i := i+1) {
if (MgcpConnectionTable[i].comp_ref == client) {
return true;
}
}
return false;
}
private function f_comp_by_conn_id(MgcpConnectionId conn_id)
runs on MGCP_Emulation_CT return MGCP_ConnHdlr {
var integer i;
for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
if (MgcpConnectionTable[i].conn_id == conn_id) {
return MgcpConnectionTable[i].comp_ref;
}
}
log("MGCP Connection Table not found by Connection Id", conn_id);
setverdict(fail);
self.stop;
}
private function f_conn_id_by_comp(MGCP_ConnHdlr client)
runs on MGCP_Emulation_CT return MgcpConnectionId {
var integer i;
for (i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
if (MgcpConnectionTable[i].comp_ref == client) {
return MgcpConnectionTable[i].conn_id;
}
}
log("MGCP Connection Table not found by component ", client);
setverdict(fail);
self.stop;
}
/* TODO: move this to MGCP_Types? */
function f_mgcp_conn_id(MgcpMessage msg) return hexstring {
var MgcpParameterList params;
var integer i;
if (ischosen(msg.command)) {
params := msg.command.params;
} else {
params := msg.response.params;
}
for (i := 0; i < lengthof(params); i := i+1) {
if (params[i].code == "I") {
return str2hex(params[i].val);
}
}
return ''H;
}
private function f_conn_table_init()
runs on MGCP_Emulation_CT {
for (var integer i := 0; i < sizeof(MgcpConnectionTable); i := i+1) {
MgcpConnectionTable[i].comp_ref := null;
MgcpConnectionTable[i].conn_id := omit;
}
}
function main(MGCPOps ops, MGCP_conn_parameters p, charstring id) runs on MGCP_Emulation_CT {
var Result res;
g_mgcp_id := id;
f_conn_table_init();
f_expect_table_init();
map(self:MGCP, system:MGCP_CODEC_PT);
if (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:={} });
}
g_mgcp_conn_id := res.connId;
while (true) {
var MGCP_ConnHdlr vc_conn;
var ExpectCriteria crit;
var MGCP_RecvFrom mrf;
var MgcpMessage msg;
var MgcpCommand cmd;
var MgcpResponse resp;
alt {
/* MGCP from client */
[] MGCP_CLIENT.receive(MgcpResponse:?) -> value resp sender vc_conn {
/* Pass message through */
msg.response := resp;
/* TODO: check which ConnectionID client has allocated + store in table? */
MGCP.send(t_MGCP_Send(g_mgcp_conn_id, msg));
}
[] MGCP.receive(tr_MGCP_RecvFrom_R(?)) -> value mrf {
if (p.callagent_udp_port == -1) {
/* we aren't yet connected to the remote side port, let's fix this */
p.callagent_udp_port := mrf.remPort;
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 (ischosen(mrf.msg.command)) {
cmd := mrf.msg.command;
if (match(cmd, tr_MgcpCommand_CO)) {
/* connection-oriented MGCP */
if (cmd.line.verb == "CRCX") {
/* TODO: allocate ConnectionID here + store in Table? */
vc_conn := ops.create_cb.apply(cmd, id);
} else {
var MgcpConnectionId conn_id := f_mgcp_conn_id(mrf.msg);
vc_conn := f_comp_by_conn_id(conn_id);
}
MGCP_CLIENT.send(cmd) to 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_Send(g_mgcp_conn_id, r));
}
}
} else {
setverdict(fail, "Received unexpected MGCP response: ", mrf.msg.response);
self.stop;
}
}
[] MGCP_PROC.getcall(MGCPEM_register:{?,?}) -> param(crit, vc_conn) {
f_create_expect(crit, vc_conn);
MGCP_PROC.reply(MGCPEM_register:{crit, 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);
type port MGCPEM_PROC_PT procedure {
inout MGCPEM_register;
} 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;
/* Ensure cmd is a CRCX? */
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);
return ret;
}
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);
self.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;
}
}
setverdict(fail, "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:{?,?}) {};
}
}
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;
}
}