Add Osmux support and tests for MGW

Depends: osmo-mgw.git Iac073f1db46569b46eddeaecc9934a2986bd50f1
Change-Id: Ibb58b2a4e08d6f30cfe347c217794d0d1310954f
This commit is contained in:
Pau Espin 2019-05-14 13:40:49 +02:00 committed by Harald Welte
parent 4832c86acd
commit b2c6b38f3f
11 changed files with 1546 additions and 8 deletions

View File

@ -49,6 +49,12 @@ module MGCP_Templates {
val := hex2str(cid)
};
/* Osmocom extension: X-Osmux: {*,%u} */
template MgcpParameter ts_MgcpParOsmuxCID(MgcpOsmuxCID osmux_cid) := {
code := "X-OSMUX",
val := f_mgcp_osmux_cid_encode(osmux_cid)
};
/* osmo-bsc_mgcp implements L/C/M/X only, osmo-mgw adds 'I' */
/* SDP: osmo-bsc_mgcp implements Tx of v,o,s,c,t,m,a */
@ -89,6 +95,18 @@ module MGCP_Templates {
sdp := sdp
}
template MgcpCommand ts_CRCX_osmux(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := {
line := t_MgcpCmdLine("CRCX", trans_id, ep),
params := {
t_MgcpParConnMode(mode),
ts_MgcpParCallId(call_id),
//t_MgcpParReqId(omit),
t_MgcpParLocConnOpt("p:20, a:AMR"),
ts_MgcpParOsmuxCID(osmux_cid)
},
sdp := sdp
}
template MgcpCommand tr_CRCX(template MgcpEndpoint ep := ?) := {
line := t_MgcpCmdLine("CRCX", ?, ep),
params := *,
@ -105,6 +123,16 @@ module MGCP_Templates {
sdp := ?
}
template MgcpResponse tr_CRCX_ACK_osmux := {
line := {
code := "200",
trans_id := ?,
string := "OK"
},
params:= { { "I", ? }, {"X-OSMUX", ?}, *},
sdp := ?
}
template MgcpResponse ts_CRCX_ACK(MgcpTransId trans_id, MgcpConnectionId conn_id, template SDP_Message sdp := omit) := {
line := {
code := "200",
@ -127,6 +155,19 @@ module MGCP_Templates {
sdp := sdp
}
template MgcpCommand ts_MDCX_osmux(MgcpTransId trans_id, charstring ep, MgcpConnectionMode mode, MgcpCallId call_id, MgcpConnectionId conn_id, MgcpOsmuxCID osmux_cid, template SDP_Message sdp := omit) := {
line := t_MgcpCmdLine("MDCX", trans_id, ep),
params := {
t_MgcpParConnMode(mode),
ts_MgcpParCallId(call_id),
ts_MgcpParConnectionId(conn_id),
//t_MgcpParReqId(omit),
t_MgcpParLocConnOpt("p:20, a:AMR"),
ts_MgcpParOsmuxCID(osmux_cid)
},
sdp := sdp
}
template MgcpCommand tr_MDCX := {
line := t_MgcpCmdLine("MDCX", ?, ?),
params := *,
@ -286,6 +327,21 @@ module MGCP_Templates {
}
}
/* -1 is wildcard, positive is translated as string */
function f_mgcp_osmux_cid_encode(MgcpOsmuxCID osmux_cid) return charstring {
if (osmux_cid == -1) {
return "*";
}
return int2str(osmux_cid);
}
function f_mgcp_osmux_cid_decode(charstring osmux_cid) return MgcpOsmuxCID {
if (osmux_cid == "*") {
return -1;
}
return str2int(osmux_cid);
}
function f_mgcp_contains_par(MgcpMessage msg, MgcpInfoCode code) return boolean {
var MgcpParameterList pars;
if (ischosen(msg.command)) {
@ -352,6 +408,10 @@ module MGCP_Templates {
return str2hex(f_MgcpCmd_extract_par(cmd, "I"));
}
function f_MgcpCmd_extract_osmux_cid(MgcpCommand cmd) return MgcpOsmuxCID {
return f_mgcp_osmux_cid_decode(f_MgcpCmd_extract_par(cmd, "X-OSMUX"));
}
function f_mgcp_alloc_tid() return MgcpTransId {
return int2str(float2int(rnd()*2147483647.0));

View File

@ -24,13 +24,14 @@ module MGCP_Types {
type hexstring MgcpCallId length(1..32); /* 3.2.2.2 */
type hexstring MgcpConnectionId length(1..32); /* 3.2.2.5 */
type hexstring MgcpRequestId length(1..32); /* 3.2.2.18 */
type integer MgcpOsmuxCID (-1 .. 255);
type charstring MgcpResponseCode (pattern "\d#(3)");
type charstring MgcpInfoCode ("B", "C", "I", "N", "X", "L", "M", "R",
"S", "D", "O", "P", "E", "Z", "Q", "T",
"RC", "LC", "A", "ES", "RM", "RD", "PL",
"MD", "X-Osmo-CP") with {
variant "TEXT_CODING(,convert=upper_case,'([BCINXLMRSDOPEZQTA])|(RC)|(LC)|(ES)|(RM)|(RD)|(PL)|(MD)|(X-Osmo-CP)',case_insensitive)"
"MD", "X-OSMO-CP", "X-OSMUX") with {
variant "TEXT_CODING(,convert=upper_case,'([BCINXLMRSDOPEZQTA])|(RC)|(LC)|(ES)|(RM)|(RD)|(PL)|(MD)|(X-OSMO-CP)|(X-OSMUX)',case_insensitive)"
};
/* 3.2.2.6 */

View File

@ -0,0 +1,74 @@
module OSMUX_CodecPort {
/* Simple Osmux Codec Port, translating between raw UDP primitives with
* octetstring payload towards the IPL4asp provider, and Osmux primitives
* which carry the decoded abstract Osmux data types as payload.
*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All rights reserved.
*
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* Released under the terms of GNU General Public License, Version 2 or
* (at your option) any later version.
*/
import from IPL4asp_PortType all;
import from IPL4asp_Types all;
import from OSMUX_Types all;
type record Osmux_RecvFrom {
ConnectionId connId,
HostName remName,
PortNumber remPort,
HostName locName,
PortNumber locPort,
OSMUX_PDU msg
};
template Osmux_RecvFrom t_Osmux_RecvFrom(template OSMUX_PDU msg) := {
connId := ?,
remName := ?,
remPort := ?,
locName := ?,
locPort := ?,
msg := msg
}
type record Osmux_Send {
ConnectionId connId,
OSMUX_PDU msg
}
template Osmux_Send t_Osmux_Send(template ConnectionId connId, template OSMUX_PDU msg) := {
connId := connId,
msg := msg
}
private function IPL4_to_Osmux_RecvFrom(in ASP_RecvFrom pin, out Osmux_RecvFrom pout) {
pout.connId := pin.connId;
pout.remName := pin.remName;
pout.remPort := pin.remPort;
pout.locName := pin.locName;
pout.locPort := pin.locPort;
pout.msg := dec_OSMUX_PDU(pin.msg)
} with { extension "prototype(fast)" };
private function Osmux_to_IPL4_Send(in Osmux_Send pin, out ASP_Send pout) {
pout.connId := pin.connId;
pout.proto := { udp := {} };
pout.msg := enc_OSMUX_PDU(pin.msg);
} with { extension "prototype(fast)" };
type port OSMUX_CODEC_PT message {
out Osmux_Send;
in Osmux_RecvFrom,
ASP_ConnId_ReadyToRelease,
ASP_Event;
} with { extension "user IPL4asp_PT
out(Osmux_Send -> ASP_Send:function(Osmux_to_IPL4_Send))
in(ASP_RecvFrom -> Osmux_RecvFrom: function(IPL4_to_Osmux_RecvFrom);
ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple;
ASP_Event -> ASP_Event: simple)"
}
}

View File

@ -0,0 +1,44 @@
module OSMUX_CodecPort_CtrlFunct {
import from OSMUX_CodecPort all;
import from IPL4asp_Types all;
external function f_IPL4_listen(
inout OSMUX_CODEC_PT portRef,
in HostName locName,
in PortNumber locPort,
in ProtoTuple proto,
in OptionList options := {}
) return Result;
external function f_IPL4_connect(
inout OSMUX_CODEC_PT portRef,
in HostName remName,
in PortNumber remPort,
in HostName locName,
in PortNumber locPort,
in ConnectionId connId,
in ProtoTuple proto,
in OptionList options := {}
) return Result;
external function f_IPL4_close(
inout OSMUX_CODEC_PT portRef,
in ConnectionId id,
in ProtoTuple proto := { unspecified := {} }
) return Result;
external function f_IPL4_setUserData(
inout OSMUX_CODEC_PT portRef,
in ConnectionId id,
in UserData userData
) return Result;
external function f_IPL4_getUserData(
inout OSMUX_CODEC_PT portRef,
in ConnectionId id,
out UserData userData
) return Result;
}

View File

@ -0,0 +1,55 @@
#include "IPL4asp_PortType.hh"
#include "OSMUX_CodecPort.hh"
#include "IPL4asp_PT.hh"
namespace OSMUX__CodecPort__CtrlFunct {
IPL4asp__Types::Result f__IPL4__listen(
OSMUX__CodecPort::OSMUX__CODEC__PT& portRef,
const IPL4asp__Types::HostName& locName,
const IPL4asp__Types::PortNumber& locPort,
const IPL4asp__Types::ProtoTuple& proto,
const IPL4asp__Types::OptionList& options)
{
return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options);
}
IPL4asp__Types::Result f__IPL4__connect(
OSMUX__CodecPort::OSMUX__CODEC__PT& portRef,
const IPL4asp__Types::HostName& remName,
const IPL4asp__Types::PortNumber& remPort,
const IPL4asp__Types::HostName& locName,
const IPL4asp__Types::PortNumber& locPort,
const IPL4asp__Types::ConnectionId& connId,
const IPL4asp__Types::ProtoTuple& proto,
const IPL4asp__Types::OptionList& options)
{
return f__IPL4__PROVIDER__connect(portRef, remName, remPort,
locName, locPort, connId, proto, options);
}
IPL4asp__Types::Result f__IPL4__close(
OSMUX__CodecPort::OSMUX__CODEC__PT& portRef,
const IPL4asp__Types::ConnectionId& connId,
const IPL4asp__Types::ProtoTuple& proto)
{
return f__IPL4__PROVIDER__close(portRef, connId, proto);
}
IPL4asp__Types::Result f__IPL4__setUserData(
OSMUX__CodecPort::OSMUX__CODEC__PT& portRef,
const IPL4asp__Types::ConnectionId& connId,
const IPL4asp__Types::UserData& userData)
{
return f__IPL4__PROVIDER__setUserData(portRef, connId, userData);
}
IPL4asp__Types::Result f__IPL4__getUserData(
OSMUX__CodecPort::OSMUX__CODEC__PT& portRef,
const IPL4asp__Types::ConnectionId& connId,
IPL4asp__Types::UserData& userData)
{
return f__IPL4__PROVIDER__getUserData(portRef, connId, userData);
}
}

View File

@ -0,0 +1,575 @@
module OSMUX_Emulation {
/* Functionalities that we want this module to implement:
* * act as a Osmux source that generates a Osmux Stream
* * act as a Osmux sink that consumes a Osmux Stream
*
* for all of the above, we want to be able to
* * specify the payload type
* * specify the interval / sample rate
* * create drop-outs in the stream
* * detect reordered or lost frames
* * validate if the size of the frames matches epectations
* * play back real audio (at least some tones?)
* * enable/disable generation/verification of RTCP
*/
/* Ideas:
* each component consists of transmitter and receiver
* transmitters and receivers can be operated as tuple?
* high-level operation
** set-up config at transmitter + receiver
** transmit sequence of payloads
** verify receiption of those payloads
* can operate full-duplex/bi-directional as needed
* transmitter
** trigger transmission of n number of packets
** transmit them at normal ptime interval
** payload size configurable
** payload contents PRBS or the like
* receiver
** count number of related packets at receiver
** check received payload type
** check received timestamp increments
** check received seq_nr increments
** (optionally) check for SSRC
** (optionally) check for payload size
** (optionally) check for payload contents
* later
** how to test transcoding?
** how to test pure play-out endpoints (rx only)?
** how to test "Rx from wrong IP/port" scenarios?
** how to test RTCP?
** maybe keep ports un-connected to show wrong src -lrt
*/
import from General_Types all;
import from Osmocom_Types all;
import from IPL4asp_Types all;
import from OSMUX_Types all;
import from OSMUX_CodecPort all;
import from OSMUX_CodecPort_CtrlFunct all;
type component OSMUX_Emulation_CT {
/* down-facing ports for Osmux on top of IPL4asp */
port OSMUX_CODEC_PT OSMUX;
var integer g_osmux_conn_id := -1;
/* user-facing port for controlling the binding */
port OsmuxEM_CTRL_PT CTRL;
/* configurable by user, should be fixed */
var OsmuxemConfig g_cfg := c_OsmuxemDefaultCfg;
/* statistics */
var OsmuxemStats g_stat := c_OsmuxemStatsReset;
var HostName g_remote_host;
var PortNumber g_remote_port;
var HostName g_local_host;
var PortNumber g_local_port;
/* state variables, change over time */
var boolean g_rx_enabled := false;
var boolean g_tx_connected := false; /* Set to true after connect() */
var INT7b g_rx_payload_type := 0;
var LIN2_BO_LAST g_rx_last_seq;
var uint32_t g_rx_last_ts;
var RxHandleTableRec RxHandleTable[16];
var OsmuxTxHandle TxHandleList[16];
}
type record RxHandleTableRec {
OsmuxCID cid,
OsmuxRxHandle vc_conn
};
type record OsmuxRxHandle {
OsmuxCID cid,
boolean first_seq_seen,
INT1 last_seq_ack
};
const OsmuxRxHandle c_OsmuxemDefaultRxHandle := {
cid := 0,
first_seq_seen := false,
last_seq_ack := 0
}
type record OsmuxTxHandle {
INT2b ft,
BIT1 amr_f,
BIT1 amr_q,
INT1 seq,
OsmuxCID cid,
INT4b amr_ft,
INT4b amr_cmr
};
template OsmuxTxHandle t_TxHandleAMR590(OsmuxCID cid) := {
ft := 1,//enum2int(OsmuxFT:OSMUX_FT_AMR)
amr_f := '0'B, /* this frame is the last frame in this payload */
amr_q := '1'B, /* frame not damaged */
seq := 12,
cid := cid,
amr_ft := 2, /* AMR 5.90 */
amr_cmr := 0
};
type enumerated OsmuxemMode {
OSMUXEM_MODE_NONE,
OSMUXEM_MODE_TXONLY,
OSMUXEM_MODE_RXONLY,
OSMUXEM_MODE_BIDIR
};
type record OsmuxemStats {
/* number of packets transmitted */
integer num_pkts_tx,
/* number of Osmux payload bytes transmitted */
integer bytes_payload_tx,
/* number of packets received */
integer num_pkts_rx,
/* number of Osmux payload bytes received */
integer bytes_payload_rx,
/* number of packets received out-of-sequence */
integer num_pkts_rx_err_seq,
/* number of packets received during Rx disable */
integer num_pkts_rx_err_disabled,
/* number of packets received with mismatching payload */
integer num_pkts_rx_err_payload
}
const OsmuxemStats c_OsmuxemStatsReset := {
num_pkts_tx := 0,
bytes_payload_tx := 0,
num_pkts_rx := 0,
bytes_payload_rx := 0,
num_pkts_rx_err_seq := 0,
num_pkts_rx_err_disabled := 0,
num_pkts_rx_err_payload := 0
}
type record OsmuxemConfig {
INT3b batch_size,
integer tx_duration_ms,
octetstring tx_fixed_payload optional,
octetstring rx_fixed_payload optional
};
const OsmuxemConfig c_OsmuxemDefaultCfg := {
batch_size := 4,
tx_duration_ms := 20 * 4, /* 4 is batch_size */
tx_fixed_payload := '010203040102030401020304010203040102030401020304'O,
rx_fixed_payload := '010203040102030401020304010203040102030401020304'O
}
signature OsmuxEM_bind(in HostName local_host, inout PortNumber local_port);
signature OsmuxEM_connect(in HostName remote_host, in PortNumber remote_port);
signature OsmuxEM_mode(in OsmuxemMode mode);
signature OsmuxEM_configure(in OsmuxemConfig cfg);
signature OsmuxEM_stats_get(out OsmuxemStats stats);
signature OsmuxEM_register_rxhandle(in OsmuxRxHandle hdl);
signature OsmuxEM_register_txhandle(in OsmuxTxHandle hdl);
type port OsmuxEM_CTRL_PT procedure {
inout OsmuxEM_bind, OsmuxEM_connect, OsmuxEM_mode, OsmuxEM_configure,
OsmuxEM_stats_get, OsmuxEM_register_rxhandle, OsmuxEM_register_txhandle;
} with { extension "internal" };
function f_osmuxem_bind(OsmuxEM_CTRL_PT pt, in HostName local_host, inout PortNumber local_port) {
pt.call(OsmuxEM_bind:{local_host, local_port}) {
[] pt.getreply(OsmuxEM_bind:{local_host, ?}) -> param (local_port) {};
}
}
function f_osmuxem_connect(OsmuxEM_CTRL_PT pt, in HostName remote_host, in PortNumber remote_port) {
pt.call(OsmuxEM_connect:{remote_host, remote_port}) {
[] pt.getreply(OsmuxEM_connect:{remote_host, remote_port}) {};
}
}
function f_osmuxem_mode(OsmuxEM_CTRL_PT pt, in OsmuxemMode mode) {
pt.call(OsmuxEM_mode:{mode}) {
[] pt.getreply(OsmuxEM_mode:{mode}) {};
}
}
function f_osmuxem_configure(OsmuxEM_CTRL_PT pt, in OsmuxemConfig cfg) {
pt.call(OsmuxEM_configure:{cfg}) {
[] pt.getreply(OsmuxEM_configure:{cfg}) {};
}
}
function f_osmuxem_stats_get(OsmuxEM_CTRL_PT pt) return OsmuxemStats {
var OsmuxemStats stats;
pt.call(OsmuxEM_stats_get:{-}) {
[] pt.getreply(OsmuxEM_stats_get:{?}) -> param(stats) {};
}
return stats;
}
function f_osmuxem_register_rxhandle(OsmuxEM_CTRL_PT pt, OsmuxRxHandle hdl) {
pt.call(OsmuxEM_register_rxhandle:{hdl}) {
[] pt.getreply(OsmuxEM_register_rxhandle:{hdl}) {};
}
}
function f_osmuxem_register_txhandle(OsmuxEM_CTRL_PT pt, OsmuxTxHandle hdl) {
pt.call(OsmuxEM_register_txhandle:{hdl}) {
[] pt.getreply(OsmuxEM_register_txhandle:{hdl}) {};
}
}
function f_osmuxem_stats_compare_value(integer a, integer b, integer tolerance := 0) return boolean {
var integer temp;
temp := (a - b)
if (temp < 0) {
temp := -temp;
}
if (temp > tolerance) {
return false;
}
return true;
}
/* Cross-compare two osmuxem-statistics. The transmission statistics on the a side
* must match the reception statistics on the other side and vice versa. The
* user may also supply a tolerance value (number of packets) when deviations
* are acceptable */
function f_osmuxem_stats_compare(OsmuxemStats a, OsmuxemStats b, integer tolerance := 0) return boolean {
var integer plen;
log("stats A: ", a);
log("stats B: ", b);
log("tolerance: ", tolerance, " packets");
if (f_osmuxem_stats_compare_value(a.num_pkts_tx, b.num_pkts_rx, tolerance) == false) {
return false;
}
if (f_osmuxem_stats_compare_value(a.num_pkts_rx, b.num_pkts_tx, tolerance) == false) {
return false;
}
if(a.num_pkts_tx > 0) {
plen := a.bytes_payload_tx / a.num_pkts_tx;
} else {
plen := 0;
}
if (f_osmuxem_stats_compare_value(a.bytes_payload_tx, b.bytes_payload_rx, tolerance * plen) == false) {
return false;
}
if (f_osmuxem_stats_compare_value(a.bytes_payload_rx, b.bytes_payload_tx, tolerance * plen) == false) {
return false;
}
return true;
}
/* Check the statistics for general signs of errors. This is a basic general
* check that will fit most situations and is intended to be executed by
* the testcases as as needed. */
function f_osmuxem_stats_err_check(OsmuxemStats s) {
log("stats: ", s);
/* Check if there was some activity at either on the RX or on the
* TX side, but complete silence would indicate some problem */
if (s.num_pkts_tx < 1 and s.num_pkts_rx < 1) {
setverdict(fail, "no Osmux packet activity detected (packets)");
mtc.stop;
}
if (s.bytes_payload_tx < 1 and s.bytes_payload_rx < 1) {
setverdict(fail, "no Osmux packet activity detected (bytes)");
mtc.stop;
}
/* Check error counters */
if (s.num_pkts_rx_err_seq != 0) {
setverdict(fail, "Osmux packet sequence number errors occurred");
mtc.stop;
}
if (s.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "Osmux packets received while RX was disabled");
mtc.stop;
}
if (s.num_pkts_rx_err_payload != 0) {
setverdict(fail, "Osmux packets with mismatching payload received");
mtc.stop;
}
}
template PDU_Osmux_AMR ts_OsmuxAMR(BIT1 marker, INT3b ctr, BIT1 amr_f, BIT1 amr_q, INT1 seq,
OsmuxCID cid, INT4b amr_ft, INT4b amr_cmr,
octetstring payload) := {
header := {
marker := marker,
ft := 1,
ctr := ctr,
amr_f := amr_f,
amr_q := amr_q,
seq := seq,
cid := cid,
amr_ft := amr_ft,
amr_cmr := amr_cmr
},
data := payload
}
private function f_rxhandle_get_by_cid(OsmuxCID cid) runs on OSMUX_Emulation_CT return OsmuxRxHandle {
var integer i;
for (i := 0; i < sizeof(RxHandleTable); i := i+1) {
if (isbound(RxHandleTable[i].cid) and RxHandleTable[i].cid == cid) {
return RxHandleTable[i].vc_conn;
}
}
setverdict(fail, "No Component for CID ", cid);
mtc.stop;
}
private function f_rxhandle_cid_add(OsmuxRxHandle hdl) runs on OSMUX_Emulation_CT {
var integer i;
for (i := 0; i < sizeof(RxHandleTable); i := i+1) {
if (not isbound(RxHandleTable[i].cid)) {
RxHandleTable[i].cid := hdl.cid;
RxHandleTable[i].vc_conn := hdl;
return;
}
}
testcase.stop("No Space in RxHandleTable for ", hdl.cid);
mtc.stop;
}
private function f_txhandle_cid_add(OsmuxTxHandle hdl) runs on OSMUX_Emulation_CT {
var integer i;
for (i := 0; i < sizeof(TxHandleList); i := i+1) {
if (not isbound(TxHandleList[i])) {
TxHandleList[i] := hdl;
return;
}
}
testcase.stop("No Space in TxHandleList for ", hdl.cid);
mtc.stop;
}
function f_osmux_gen_expected_rx_rtp_payload(INT4b amr_ft, octetstring tx_fixed_payload) return octetstring {
var integer payload_len;
var octetstring payload_truncated;
var integer i;
payload_len := f_amrft_payload_len(amr_ft);
payload_truncated := substr(tx_fixed_payload, 0, payload_len);
return payload_truncated;
}
private function f_osmux_gen_payload(INT3b ctr, INT4b amr_ft) runs on OSMUX_Emulation_CT return octetstring {
var octetstring payload_truncated := ''O;
var integer i;
for (i := 0; i < ctr + 1; i := i+1) {
payload_truncated := payload_truncated & f_osmux_gen_expected_rx_rtp_payload(amr_ft, g_cfg.tx_fixed_payload);
}
return payload_truncated;
}
private function f_tx_osmux(integer i, INT3b ctr, octetstring payload, BIT1 marker := '0'B) runs on OSMUX_Emulation_CT {
var OsmuxTxHandle hdl := TxHandleList[i];
var PDU_Osmux_AMR osmux_amr := valueof(ts_OsmuxAMR(marker, ctr, hdl.amr_f,
hdl.amr_q, hdl.seq, hdl.cid, hdl.amr_ft,
hdl.amr_cmr, payload));
OSMUX.send(t_Osmux_Send(g_osmux_conn_id, OSMUX_PDU:{osmux_amr:=osmux_amr}));
/* increment sequence + timestamp for next transmit */
TxHandleList[i].seq := TxHandleList[i].seq + 1;
/* update counters */
g_stat.num_pkts_tx := g_stat.num_pkts_tx+1;
g_stat.bytes_payload_tx := g_stat.bytes_payload_tx +
lengthof(payload);
}
private function f_tx_osmux_all_cid(BIT1 marker := '0'B) runs on OSMUX_Emulation_CT {
/* TODO: append all in one UDP packet and send together */
var integer i;
var octetstring payload_truncated;
var INT3b ctr := g_cfg.batch_size - 1;
for (i := 0; i < sizeof(TxHandleList); i := i+1) {
if (isbound(TxHandleList[i])) {
payload_truncated := f_osmux_gen_payload(ctr, TxHandleList[i].amr_ft);
f_tx_osmux(i, ctr, payload_truncated, marker);
}
}
}
function f_main() runs on OSMUX_Emulation_CT
{
var Result res;
var OsmuxRxHandle rx_hdl;
var OsmuxTxHandle tx_hdl;
var octetstring payload_truncated;
timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0;
var Osmux_RecvFrom rx_osmux;
var PDU_Osmux_AMR rx_amr;
var PDU_Osmux_DUMMY osmux_dummy;
var OsmuxemConfig cfg;
var template Osmux_RecvFrom tr_osmux_amr := {
connId := ?,
remName := ?,
remPort := ?,
locName := ?,
locPort := ?,
msg := ?
};
tr_osmux_amr.msg := { osmux_amr := ? };
var template Osmux_RecvFrom tr_osmux_dummy := {
connId := ?,
remName := ?,
remPort := ?,
locName := ?,
locPort := ?,
msg := ?
};
tr_osmux_dummy.msg := { osmux_dummy := ? };
while (true) {
alt {
/* control procedures (calls) from the user */
[] CTRL.getcall(OsmuxEM_bind:{?,?}) -> param(g_local_host, g_local_port) {
g_tx_connected := false; /* will set it back to true upon next connect() call */
res := OSMUX_CodecPort_CtrlFunct.f_IPL4_listen(OSMUX, g_local_host,
g_local_port, {udp:={}});
if (not ispresent(res.connId)) {
setverdict(fail, "Could not listen on Osmux socket, check your configuration");
mtc.stop;
}
g_osmux_conn_id := res.connId;
tr_osmux_amr.connId := g_osmux_conn_id;
tr_osmux_dummy.connId := g_osmux_conn_id;
CTRL.reply(OsmuxEM_bind:{g_local_host, g_local_port});
}
[] CTRL.getcall(OsmuxEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) {
res := OSMUX_CodecPort_CtrlFunct.f_IPL4_connect(OSMUX, g_remote_host,
g_remote_port,
g_local_host, g_local_port,
g_osmux_conn_id, {udp:={}});
if (not ispresent(res.connId)) {
setverdict(fail, "Could not connect to Osmux socket, check your configuration");
mtc.stop;
}
g_tx_connected := true;
CTRL.reply(OsmuxEM_connect:{g_remote_host, g_remote_port});
}
[] CTRL.getcall(OsmuxEM_mode:{OSMUXEM_MODE_NONE}) {
T_transmit.stop;
g_rx_enabled := false;
CTRL.reply(OsmuxEM_mode:{OSMUXEM_MODE_NONE});
}
[] CTRL.getcall(OsmuxEM_mode:{OSMUXEM_MODE_TXONLY}) {
/* start transmit timer */
T_transmit.start;
g_rx_enabled := false;
CTRL.reply(OsmuxEM_mode:{OSMUXEM_MODE_TXONLY});
}
[] CTRL.getcall(OsmuxEM_mode:{OSMUXEM_MODE_RXONLY}) {
T_transmit.stop;
if (g_rx_enabled == false) {
/* flush queues */
OSMUX.clear;
g_rx_enabled := true;
}
CTRL.reply(OsmuxEM_mode:{OSMUXEM_MODE_RXONLY});
}
[] CTRL.getcall(OsmuxEM_mode:{OSMUXEM_MODE_BIDIR}) {
T_transmit.start;
if (g_rx_enabled == false) {
/* flush queues */
OSMUX.clear;
g_rx_enabled := true;
}
CTRL.reply(OsmuxEM_mode:{OSMUXEM_MODE_BIDIR});
}
[] CTRL.getcall(OsmuxEM_configure:{?}) -> param (cfg) {
g_cfg := cfg;
CTRL.reply(OsmuxEM_configure:{cfg});
}
[] CTRL.getcall(OsmuxEM_register_txhandle:{?}) -> param (tx_hdl) {
f_txhandle_cid_add(tx_hdl);
CTRL.reply(OsmuxEM_register_txhandle:{tx_hdl});
}
[] CTRL.getcall(OsmuxEM_register_rxhandle:{?}) -> param (rx_hdl) {
f_rxhandle_cid_add(rx_hdl);
CTRL.reply(OsmuxEM_register_rxhandle:{rx_hdl});
}
[] CTRL.getcall(OsmuxEM_stats_get:{?}) {
CTRL.reply(OsmuxEM_stats_get:{g_stat});
}
/* simply ignore any Osmux AMR if receiver not enabled */
[g_rx_enabled==false] OSMUX.receive(tr_osmux_amr) {
g_stat.num_pkts_rx_err_disabled := g_stat.num_pkts_rx_err_disabled+1;
}
/* simply ignore any Osmux Dummy if receiver not enabled */
[g_rx_enabled==false] OSMUX.receive(tr_osmux_dummy) -> value rx_osmux {
log("Osmux Dummy received on CID ", rx_osmux.msg.osmux_dummy.header.cid, " (rx_disabled)");
}
/* process received Osmux AMR if receiver enabled */
[g_rx_enabled] OSMUX.receive(tr_osmux_amr) -> value rx_osmux {
/* increment counters */
g_stat.num_pkts_rx := g_stat.num_pkts_rx+1;
g_stat.bytes_payload_rx := g_stat.bytes_payload_rx +
lengthof(rx_osmux.msg.osmux_amr.data);
rx_hdl := f_rxhandle_get_by_cid(rx_osmux.msg.osmux_amr.header.cid);
if (rx_hdl.first_seq_seen and rx_hdl.last_seq_ack != rx_osmux.msg.osmux_amr.header.seq - 1 ) {
g_stat.num_pkts_rx_err_seq := g_stat.num_pkts_rx_err_seq + 1;
}
rx_hdl.first_seq_seen := true;
rx_hdl.last_seq_ack := rx_osmux.msg.osmux_amr.header.seq;
payload_truncated := f_osmux_gen_payload(rx_osmux.msg.osmux_amr.header.ctr, rx_osmux.msg.osmux_amr.header.amr_ft);
if (ispresent(g_cfg.rx_fixed_payload) and rx_osmux.msg.osmux_amr.data != payload_truncated) {
g_stat.num_pkts_rx_err_payload := g_stat.num_pkts_rx_err_payload + 1;
}
}
/* process received Osmux Dummy if receiver enabled */
[g_rx_enabled] OSMUX.receive(tr_osmux_dummy) -> value rx_osmux {
log("Osmux Dummy received on CID", rx_osmux.msg.osmux_dummy.header.cid);
rx_hdl := f_rxhandle_get_by_cid(rx_osmux.msg.osmux_dummy.header.cid);
}
/* transmit if timer has expired */
[g_tx_connected] T_transmit.timeout {
/* send one Osmux frame, re-start timer */
f_tx_osmux_all_cid();
T_transmit.start;
}
/* fail on any unexpected messages */
[] OSMUX.receive {
setverdict(fail, "Received unexpected type from Osmux");
mtc.stop;
}
}
}
}
}

110
library/OSMUX_Types.ttcn Normal file
View File

@ -0,0 +1,110 @@
/*
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All rights reserved.
*
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* Released under the terms of GNU General Public License, Version 2 or
* (at your option) any later version.
*/
module OSMUX_Types {
import from General_Types all;
external function enc_OSMUX_PDU ( in OSMUX_PDU msg ) return octetstring
with { extension "prototype(convert) encode(RAW)" };
external function dec_OSMUX_PDU ( in octetstring msg ) return OSMUX_PDU
with { extension "prototype(convert) decode(RAW)" };
type INT1 OsmuxCID (0 .. 255);
type enumerated OsmuxFT {
OSMUX_FT_LAPD,
OSMUX_FT_AMR,
OSMUX_FT_DUMMY
};
type record Osmux_AMR_header {
BIT1 marker,
INT2b ft,
INT3b ctr,
BIT1 amr_f,
BIT1 amr_q,
INT1 seq,
OsmuxCID cid,
INT4b amr_ft,
INT4b amr_cmr
} with {
variant "FIELDORDER(msb)"
}
type record PDU_Osmux_AMR {
Osmux_AMR_header header,
octetstring data
} with {
variant "FIELDORDER(msb)"
};
type record PDU_Osmux_DUMMY {
Osmux_AMR_header header,
octetstring data
} with {
variant "FIELDORDER(msb)"
};
type record Osmux_session_par {
integer id optional,
charstring local_address optional,
integer local_port optional,
charstring dest_address optional,
integer dest_port optional
}
type record ASP_Osmux_Open_session {
Osmux_session_par session_id
}
type record ASP_Osmux_Open_session_result {
Osmux_session_par session_id
}
type record ASP_Osmux_Close_session {
Osmux_session_par session_id
}
type union OSMUX_PDU {
PDU_Osmux_AMR osmux_amr,
PDU_Osmux_DUMMY osmux_dummy
} with {
variant "TAG (
osmux_amr, header.ft = 1;
osmux_dummy, header.ft = 2;
)"
};
/* AMR voice frame type identifiers
* See also 3GPP TS 26.101, Table 1a: Interpretation of Frame Type, Mode
* Indication and Mode Request fields */
type enumerated AMRFT {
AMR_FT_0, /* 4.75 */
AMR_FT_1, /* 5.15 */
AMR_FT_2, /* 5.90 */
AMR_FT_3, /* 6.70 */
AMR_FT_4, /* 7.40 */
AMR_FT_5, /* 7.95 */
AMR_FT_6, /* 10.2 */
AMR_FT_7, /* 12.2 */
AMR_FT_SID /* SID */
};
/* AMR voice frame length (in bytes, rounded),
* See also RFC 3267, chapter 3.6 */
const integer c_AMRFT_len[9] := {12, 13, 15, 17, 19, 20, 26, 31, 5};
function f_amrft_payload_len(INT4b amr_ft) return integer {
return c_AMRFT_len[amr_ft];
}
} with { encode "RAW"}

View File

@ -8,6 +8,10 @@ module MGCP_Test {
import from RTP_CodecPort all;
import from RTP_CodecPort_CtrlFunct all;
import from RTP_Emulation all;
import from OSMUX_Types all;
import from OSMUX_CodecPort all;
import from OSMUX_CodecPort_CtrlFunct all;
import from OSMUX_Emulation all;
import from IPL4asp_Types all;
import from General_Types all;
import from Native_Functions all;
@ -31,6 +35,9 @@ module MGCP_Test {
var RTP_Emulation_CT vc_RTPEM[3];
port RTPEM_CTRL_PT RTPEM[3];
var OSMUX_Emulation_CT vc_OsmuxEM;
port OsmuxEM_CTRL_PT OsmuxEM;
port TELNETasp_PT MGWVTY;
};
@ -50,12 +57,29 @@ module MGCP_Test {
PortNumber mp_remote_udp_port := 2427;
charstring mp_remote_ip := "127.0.0.1";
PortNumber mp_local_rtp_port_base := 10000;
PortNumber mp_local_osmux_port := 1985;
}
private function f_init_vty() runs on dummy_CT {
private function f_vty_enable_osmux(boolean osmux_on) runs on dummy_CT {
/* Turn on conversion mode */
f_vty_enter_config(MGWVTY);
f_vty_transceive(MGWVTY, "mgcp");
if (osmux_on) {
f_vty_transceive(MGWVTY, "osmux on");
} else {
f_vty_transceive(MGWVTY, "osmux off");
}
f_vty_transceive(MGWVTY, "exit");
f_vty_transceive(MGWVTY, "exit");
}
private function f_init_vty(boolean osmux_on) runs on dummy_CT {
map(self:MGWVTY, system:MGWVTY);
f_vty_set_prompts(MGWVTY);
f_vty_transceive(MGWVTY, "enable");
f_vty_enable_osmux(osmux_on);
}
private function f_rtpem_init(inout RTP_Emulation_CT comp_ref, integer i)
@ -66,10 +90,17 @@ module MGCP_Test {
comp_ref.start(RTP_Emulation.f_main());
}
private function f_osmuxem_init(inout OSMUX_Emulation_CT comp_ref)
runs on dummy_CT {
comp_ref := OSMUX_Emulation_CT.create("OsmuxEM");
map(comp_ref:OSMUX, system:OSMUX);
comp_ref.start(OSMUX_Emulation.f_main());
}
/* initialization function, called by each test case at the
* beginning, but 'initialized' variable ensures its body is
* only executed once */
private function f_init(template MgcpEndpoint ep := omit) runs on dummy_CT {
private function f_init(template MgcpEndpoint ep := omit, boolean osmux_on := false) runs on dummy_CT {
var Result res;
var uint32_t ssrc;
@ -93,6 +124,10 @@ module MGCP_Test {
f_rtpem_init(vc_RTPEM[i], i);
connect(vc_RTPEM[i]:CTRL, self:RTPEM[i]);
}
if (osmux_on) {
f_osmuxem_init(vc_OsmuxEM);
connect(vc_OsmuxEM:CTRL, self:OsmuxEM);
}
}
if (isvalue(ep)) {
@ -100,7 +135,7 @@ module MGCP_Test {
f_dlcx_ignore(valueof(ep));
}
f_init_vty();
f_init_vty(osmux_on);
}
testcase TC_selftest() runs on dummy_CT {
@ -272,6 +307,10 @@ module MGCP_Test {
charstring codec,
MgcpConnectionId mgcp_conn_id optional,
RtpemConfig rtp_cfg optional,
boolean osmux_cid_sent, /* whther non wildcarded CID was already sent to MGW */
MgcpOsmuxCID osmux_cid optional,
MgcpOsmuxCID osmux_cid_response optional,
OsmuxemConfig osmux_cfg optional,
charstring fmtp optional
}
@ -333,6 +372,91 @@ module MGCP_Test {
f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Create an Osmux flow (bidirectional, or receive-only) */
function f_flow_create_osmux(OsmuxEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow,
boolean one_phase := true)
runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var SDP_attribute_list attributes;
var OsmuxTxHandle tx_hdl;
var OsmuxRxHandle rx_hdl;
var charstring cid_response;
var OsmuxCID cid_resp_parsed
attributes := { valueof(ts_SDP_rtpmap(flow.pt, flow.codec)), valueof(ts_SDP_ptime(20)) };
if (isvalue(flow.fmtp)) {
attributes := attributes & { valueof(ts_SDP_fmtp(flow.pt, flow.fmtp)) };
}
/* bind local Osmux emulation socket */
f_osmuxem_bind(pt, flow.em.hostname, flow.em.portnr);
/* configure osmux-emulation */
if (ispresent(flow.osmux_cfg)) {
f_osmuxem_configure(pt, flow.osmux_cfg);
} else {
var OsmuxemConfig osmux_cfg := c_OsmuxemDefaultCfg;
f_osmuxem_configure(pt, osmux_cfg);
flow.osmux_cfg := osmux_cfg
}
if (one_phase) {
/* Connect flow to MGW using a CRCX that also contains an SDP
* part that tells the MGW where we are listening for Osmux streams
* that come from the MGW. We get a fully working connection in
* one go. */
rx_hdl := c_OsmuxemDefaultRxHandle;
rx_hdl.cid := flow.osmux_cid;
f_osmuxem_register_rxhandle(pt, rx_hdl);
flow.osmux_cid_sent := true;
cmd := ts_CRCX_osmux(get_next_trans_id(), ep, mode, call_id, flow.osmux_cid);
cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
flow.em.portnr, { int2str(flow.pt) }, attributes);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK_osmux);
flow.mgcp_conn_id := extract_conn_id(resp);
/* extract port number from response */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
} else {
/* Create a half-open connection only. We do not tell the MGW
* where it can send Osmux streams to us. This means this
* connection will only be able to receive but can not send
* data back to us. In order to turn the connection in a fully
* bi-directional one, a separate MDCX is needed. */
cmd := ts_CRCX_osmux(get_next_trans_id(), ep, mode, call_id, flow.osmux_cid);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK_osmux);
flow.mgcp_conn_id := extract_conn_id(resp);
/* extract MGW-side port number from response */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
}
/* extract Osmux CID we got assigned by the MGW */
var MgcpMessage resp_msg := {
response := resp
}
if (f_mgcp_find_param(resp_msg, "X-OSMUX", cid_response) == false) {
setverdict(fail, "No Osmux CID in MGCP response", resp);
mtc.stop;
}
/* Make sure response is no wildcard */
flow.osmux_cid_response := f_mgcp_osmux_cid_decode(cid_response);
if (flow.osmux_cid_response == -1) {
setverdict(fail, "Osmux CID in MGCP response contains unexpected wildcard");
mtc.stop;
}
tx_hdl := valueof(t_TxHandleAMR590(flow.osmux_cid_response));
f_osmuxem_register_txhandle(pt, tx_hdl);
/* finally, connect the emulation-side RTP socket to the MGW */
f_osmuxem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Modify an existing RTP flow */
function f_flow_modify(RTPEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow)
runs on dummy_CT {
@ -372,6 +496,76 @@ module MGCP_Test {
f_rtpem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Modify an existing Osmux flow */
function f_flow_modify_osmux(OsmuxEM_CTRL_PT pt, MgcpEndpoint ep, MgcpCallId call_id, charstring mode, inout RtpFlowData flow)
runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
var SDP_attribute_list attributes;
var OsmuxRxHandle rx_hdl;
var charstring cid_response;
var OsmuxCID cid_resp_parsed
attributes := { valueof(ts_SDP_rtpmap(flow.pt, flow.codec)), valueof(ts_SDP_ptime(20)) };
if (isvalue(flow.fmtp)) {
attributes := attributes & { valueof(ts_SDP_fmtp(flow.pt, flow.fmtp)) };
}
/* rebind local Osmux emulation socket to the new address */
f_osmuxem_bind(pt, flow.em.hostname, flow.em.portnr);
/* configure osmux-emulation */
if (ispresent(flow.osmux_cfg)) {
f_osmuxem_configure(pt, flow.osmux_cfg);
} else {
var OsmuxemConfig osmux_cfg := c_OsmuxemDefaultCfg;
f_osmuxem_configure(pt, osmux_cfg);
}
/* We didn't send a non-wildcarded Osmux CID yet. If caller wants to submit it, register handler */
if (flow.osmux_cid_sent == false and flow.osmux_cid != -1) {
rx_hdl := c_OsmuxemDefaultRxHandle;
rx_hdl.cid := flow.osmux_cid;
f_osmuxem_register_rxhandle(pt, rx_hdl);
flow.osmux_cid_sent := true;
}
/* connect MGW side Osmux socket to the emulation-side Osmux socket using SDP */
cmd := ts_MDCX_osmux(get_next_trans_id(), ep, mode, call_id, flow.mgcp_conn_id, flow.osmux_cid);
cmd.sdp := ts_SDP(flow.em.hostname, flow.em.hostname, "23", "42",
flow.em.portnr, { int2str(flow.pt) }, attributes);
resp := mgcp_transceive_mgw(cmd, tr_MDCX_ACK);
/* extract MGW-side port number from response. (usually this
* will not change, but thats is up to the MGW) */
flow.mgw.portnr :=
resp.sdp.media_list[0].media_field.ports.port_number;
/* extract Osmux CID we got assigned by the MGW */
var MgcpMessage resp_msg := {
response := resp
}
if (f_mgcp_find_param(resp_msg, "X-OSMUX", cid_response) == false) {
setverdict(fail, "No Osmux CID in MGCP response", resp);
mtc.stop;
}
/* Make sure response is no wildcard */
cid_resp_parsed := f_mgcp_osmux_cid_decode(cid_response);
if (cid_resp_parsed == -1) {
setverdict(fail, "Osmux CID in MGCP response contains unexpected wildcard");
mtc.stop;
}
if (cid_resp_parsed != flow.osmux_cid_response) {
setverdict(fail, "Osmux CID in MGCP MDCX response changed from prev value");
mtc.stop;
}
/* reconnect the emulation-side Osmux socket to the MGW */
f_osmuxem_connect(pt, flow.mgw.hostname, flow.mgw.portnr);
}
/* Delete an existing RTP flow */
function f_flow_delete(RTPEM_CTRL_PT pt, template MgcpEndpoint ep := omit, template MgcpCallId call_id := omit)
runs on dummy_CT {
@ -388,6 +582,22 @@ module MGCP_Test {
}
}
/* Delete an existing Osmux flow */
function f_flow_delete_osmux(OsmuxEM_CTRL_PT pt, template MgcpEndpoint ep := omit, template MgcpCallId call_id := omit)
runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
/* Switch off Osmux flow */
f_osmuxem_mode(pt, OSMUXEM_MODE_NONE);
/* Delete connection on MGW (if needed) */
if (isvalue(call_id) and isvalue(ep)) {
f_sleep(0.1);
f_dlcx_ok(valueof(ep), call_id);
}
}
function f_crcx(charstring ep_prefix) runs on dummy_CT {
var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
var template MgcpCommand cmd;
@ -437,6 +647,42 @@ module MGCP_Test {
}
}
function f_crcx_osmux(charstring ep_prefix, MgcpOsmuxCID osmux_cid, boolean run_init := true) runs on dummy_CT {
var MgcpEndpoint ep := ep_prefix & "2@" & c_mgw_domain;
var template MgcpCommand cmd;
var MgcpResponse resp;
var MgcpCallId call_id := '1234'H;
var charstring cid_response;
if (run_init) {
f_init(ep, true);
}
/* create the connection on the MGW */
cmd := ts_CRCX_osmux(get_next_trans_id(), ep, "recvonly", call_id, osmux_cid);
resp := mgcp_transceive_mgw(cmd, tr_CRCX_ACK_osmux);
extract_conn_id(resp);
/* extract Osmux CID we got assigned by the MGW */
var MgcpMessage resp_msg := {
response := resp
}
if (f_mgcp_find_param(resp_msg, "X-OSMUX", cid_response) == false) {
setverdict(fail, "No Osmux CID in MGCP response", resp);
mtc.stop;
}
/* Make sure response is no wildcard */
if (f_mgcp_osmux_cid_decode(cid_response) == -1) {
setverdict(fail, "Osmux CID in MGCP response contains unexpected wildcard");
mtc.stop;
}
/* clean-up */
f_dlcx_ok(ep, call_id);
}
/* test valid CRCX without SDP */
testcase TC_crcx() runs on dummy_CT {
f_crcx(c_mgw_ep_rtpbridge);
@ -791,6 +1037,358 @@ module MGCP_Test {
setverdict(pass);
}
/* test valid CRCX without SDP */
testcase TC_crcx_osmux_wildcard() runs on dummy_CT {
f_crcx_osmux(c_mgw_ep_rtpbridge, -1);
setverdict(pass);
}
/* test valid CRCX without SDP */
testcase TC_crcx_osmux_fixed() runs on dummy_CT {
f_crcx_osmux(c_mgw_ep_rtpbridge, 2);
setverdict(pass);
}
/* test valid CRCX without SDP, twice, to make sure CID is freed fine during first step. */
testcase TC_crcx_osmux_fixed_twice() runs on dummy_CT {
f_crcx_osmux(c_mgw_ep_rtpbridge, 3, true);
f_crcx_osmux(c_mgw_ep_rtpbridge, 3, false);
setverdict(pass);
}
/* Create one half open connection in receive-only mode. The MGW must accept
* the packets but must not send any. */
testcase TC_one_crcx_receive_only_osmux() runs on dummy_CT {
var RtpFlowData flow;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var OsmuxemStats stats;
var OsmuxTxHandle tx_hdl;
f_init(ep, true);
flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 112, "AMR/8000/1"));
flow.em.portnr := mp_local_osmux_port;
flow.osmux_cid := -1;
f_flow_create_osmux(OsmuxEM, ep, call_id, "recvonly", flow, false);
/* create a transmitter not yet known by MGW */
tx_hdl := valueof(t_TxHandleAMR590(2));
f_osmuxem_register_txhandle(OsmuxEM, tx_hdl);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_TXONLY);
f_sleep(1.0);
f_flow_delete_osmux(OsmuxEM, ep, call_id);
stats := f_osmuxem_stats_get(OsmuxEM);
if (stats.num_pkts_tx < 40 / flow.osmux_cfg.batch_size) {
setverdict(fail);
}
if (stats.bytes_payload_tx < stats.num_pkts_tx * f_amrft_payload_len(tx_hdl.amr_ft) * flow.osmux_cfg.batch_size) {
setverdict(fail);
}
f_osmuxem_stats_err_check(stats);
setverdict(pass);
}
/* Create one connection in loopback mode, test if the Osmux packets are
* actually reflected */
testcase TC_one_crcx_loopback_osmux() runs on dummy_CT {
var RtpFlowData flow;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "1@" & c_mgw_domain;
var MgcpCallId call_id := '1225'H;
var OsmuxemStats stats;
var OsmuxTxHandle tx_hdl;
f_init(ep, true);
flow := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 111, "GSM-HR-08/8000/1"));
flow.em.portnr := mp_local_osmux_port;
flow.osmux_cid := 2;
f_flow_create_osmux(OsmuxEM, ep, call_id, "loopback", flow);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_BIDIR);
f_sleep(1.0);
/* Switch off both Tx, wait to receive delayed frames from MGW */
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_RXONLY);
f_sleep(0.1);
f_flow_delete_osmux(OsmuxEM, ep, call_id);
stats := f_osmuxem_stats_get(OsmuxEM);
if (stats.num_pkts_tx != stats.num_pkts_rx) {
setverdict(fail);
}
if (stats.bytes_payload_tx != stats.bytes_payload_rx) {
setverdict(fail);
}
f_osmuxem_stats_err_check(stats);
setverdict(pass);
}
/* Cross-compare two osmuxem-statistics. The transmission statistics on the a side
* must match the reception statistics on the other side and vice versa. The
* user may also supply a tolerance value (number of packets) when deviations
* are acceptable */
function f_rtp_osmux_stats_compare(RtpemStats a, OsmuxemStats b, integer batch_size, integer tolerance := 0) return boolean {
var integer plen;
log("stats A: ", a);
log("stats B: ", b);
log("tolerance: ", tolerance, " packets");
log("batch_size: ", batch_size, " packets");
var integer tolerance_batch := tolerance + (batch_size - tolerance mod batch_size);
if (f_osmuxem_stats_compare_value(a.num_pkts_tx, b.num_pkts_rx * batch_size, tolerance_batch) == false) {
return false;
}
if (f_osmuxem_stats_compare_value(a.num_pkts_rx / batch_size, b.num_pkts_tx, tolerance_batch) == false) {
return false;
}
if(a.num_pkts_tx > 0) {
plen := a.bytes_payload_tx / a.num_pkts_tx;
} else {
plen := 0;
}
/* Each RTP pcket payload contains 2 extra bytes due to AMR ToC at start */
if (f_osmuxem_stats_compare_value(a.bytes_payload_tx, b.bytes_payload_rx + a.num_pkts_tx * 2, tolerance_batch * plen) == false) {
log("incorrect payload A->B: " , a.bytes_payload_tx, " vs ", b.bytes_payload_rx + a.num_pkts_rx * 2);
return false;
}
if (f_osmuxem_stats_compare_value(a.bytes_payload_rx, b.bytes_payload_tx + b.num_pkts_tx * 2 * batch_size, tolerance_batch * plen) == false) {
log("incorrect payload B->A: " , b.bytes_payload_tx + b.num_pkts_tx * 2 * batch_size, " vs ", a.bytes_payload_rx);
return false;
}
return true;
}
function f_TC_two_crcx_and_rtp_osmux(boolean bidir) runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats_rtp;
var OsmuxemStats stats_osmux;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1226'H;
var integer tolerance := 0;
f_init(ep, true);
/* from us to MGW */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 112, "AMR/8000"));
flow[0].rtp_cfg := c_RtpemDefaultCfg
flow[0].rtp_cfg.tx_payload_type := flow[0].pt;
/* 0014 is the ToC (CMR=AMR4.75) in front of AMR Payload in RTP Payload */
flow[0].rtp_cfg.rx_fixed_payload := '0014'O & f_osmux_gen_expected_rx_rtp_payload(2 /* AMR_FT_2, 5.90 */, c_OsmuxemDefaultCfg.tx_fixed_payload);
flow[0].rtp_cfg.tx_fixed_payload := flow[0].rtp_cfg.rx_fixed_payload;
/* bind local RTP emulation sockets */
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
/* from MGW back to us */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "AMR/8000"));
flow[1].em.portnr := mp_local_osmux_port;
flow[1].osmux_cid := 2;
flow[1].osmux_cfg := c_OsmuxemDefaultCfg;
f_flow_create_osmux(OsmuxEM, ep, call_id, "sendrecv", flow[1]);
if (bidir) {
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_BIDIR);
/* Note: When we test bidirectional we may
* loose packets during switch off because
* both ends are transmitting and we only
* can switch them off one by one. */
tolerance := 3;
} else {
f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_TXONLY);
}
f_sleep(1.0);
/* Switch off both Tx, wait to receive delayed frames from MGW */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_RXONLY);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_RXONLY);
f_sleep(0.1);
f_flow_delete_osmux(OsmuxEM, ep, call_id);
f_flow_delete(RTPEM[1]);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
if (not f_rtp_osmux_stats_compare(stats_rtp, stats_osmux, flow[1].osmux_cfg.batch_size, tolerance)) {
setverdict(fail, "RTP and Osmux endpoint statistics don't match");
mtc.stop;
}
f_rtpem_stats_err_check(stats_rtp);
f_osmuxem_stats_err_check(stats_osmux);
setverdict(pass);
}
/* create one RTP and one OSmux emulations; create two connections on MGW EP, exchange some data */
testcase TC_two_crcx_and_rtp_osmux() runs on dummy_CT {
f_TC_two_crcx_and_rtp_osmux(false);
}
/* create one RTP and one OSmux emulations; create two connections on MGW EP,
* exchange some data in both directions */
testcase TC_two_crcx_and_rtp_osmux_bidir() runs on dummy_CT {
f_TC_two_crcx_and_rtp_osmux(true);
}
function f_two_crcx_mdcx_and_rtp_osmux(boolean crcx_osmux_wildcard) runs on dummy_CT {
var RtpFlowData flow[2];
var RtpemStats stats_rtp;
var OsmuxemStats stats_osmux;
var MgcpResponse resp;
var MgcpEndpoint ep := c_mgw_ep_rtpbridge & "2@" & c_mgw_domain;
var MgcpCallId call_id := '1227'H;
var integer num_pkts_tx[2];
var integer temp;
f_init(ep, true);
/* Create the first connection in receive only mode */
flow[0] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 112, "AMR/8000"));
flow[0].rtp_cfg := c_RtpemDefaultCfg
flow[0].rtp_cfg.tx_payload_type := flow[0].pt;
/* 0014 is the ToC (CMR=AMR4.75) in front of AMR Payload in RTP Payload */
flow[0].rtp_cfg.rx_fixed_payload := '0014'O & f_osmux_gen_expected_rx_rtp_payload(2 /* AMR_FT_2, 5.90 */, c_OsmuxemDefaultCfg.tx_fixed_payload);
flow[0].rtp_cfg.tx_fixed_payload := flow[0].rtp_cfg.rx_fixed_payload;
/* bind local RTP emulation sockets */
flow[0].em.portnr := 10000;
f_flow_create(RTPEM[0], ep, call_id, "recvonly", flow[0], false);
/* Create the second connection. This connection will be also
* in receive only mode */
flow[1] := valueof(t_RtpFlow(mp_local_ip, mp_remote_ip, 110, "AMR/8000"));
flow[1].em.portnr := mp_local_osmux_port;
if (crcx_osmux_wildcard) {
flow[1].osmux_cid := -1;
} else {
flow[1].osmux_cid := 2;
}
flow[1].osmux_cfg := c_OsmuxemDefaultCfg;
f_flow_create_osmux(OsmuxEM, ep, call_id, "recvonly", flow[1], false);
/* The first leg starts transmitting */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_TXONLY);
f_sleep(0.5);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
if (stats_rtp.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from RTP MGW on recvonly connection");
mtc.stop;
}
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
if (stats_osmux.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from Osmux MGW on recvonly connection");
mtc.stop;
}
/* The second leg starts transmitting a little later */
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_TXONLY);
f_sleep(1.0);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
if (stats_rtp.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from RTP MGW on recvonly connection");
mtc.stop;
}
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
if (stats_osmux.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from Osmux MGW on recvonly connection");
mtc.stop;
}
/* The first leg will now be switched into bidirectional
* mode, but we do not expect any data comming back yet. */
f_flow_modify(RTPEM[0], ep, call_id, "sendrecv", flow[0]);
f_sleep(0.5);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
if (stats_rtp.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from RTP MGW on recvonly connection");
mtc.stop;
}
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
if (stats_osmux.num_pkts_rx_err_disabled != 0) {
setverdict(fail, "received packets from Osmux MGW on recvonly connection");
mtc.stop;
}
/* When the second leg is switched into bidirectional mode
* as well, then the MGW will connect the two together and
* we should see RTP streams passing through from both ends. */
f_rtpem_mode(RTPEM[0], RTPEM_MODE_BIDIR);
f_osmuxem_mode(OsmuxEM, OSMUXEM_MODE_BIDIR);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
num_pkts_tx[0] := stats_rtp.num_pkts_tx
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
num_pkts_tx[1] := stats_osmux.num_pkts_tx
if (crcx_osmux_wildcard) {
/* For now we must set same CID as the MGW recvCID,
* having sendCID!=recvCID is not yet supported. */
flow[1].osmux_cid := flow[1].osmux_cid_response;
}
f_flow_modify_osmux(OsmuxEM, ep, call_id, "sendrecv", flow[1]);
f_sleep(2.0);
stats_rtp := f_rtpem_stats_get(RTPEM[0]);
stats_osmux := f_osmuxem_stats_get(OsmuxEM);
temp := stats_rtp.num_pkts_tx - num_pkts_tx[0] - stats_osmux.num_pkts_rx * flow[1].osmux_cfg.batch_size;
if (temp > 3 * flow[1].osmux_cfg.batch_size or temp < -3 * flow[1].osmux_cfg.batch_size) {
log("stats_rtp: ", stats_rtp);
log("stats_osmux: ", stats_osmux);
log("old_rtp_tx: ", num_pkts_tx[0]);
setverdict(fail, "number of packets not within normal parameters (" & int2str(temp) & ")");
mtc.stop;
}
temp := stats_osmux.num_pkts_tx - num_pkts_tx[1] - stats_rtp.num_pkts_rx / flow[1].osmux_cfg.batch_size;
if (temp > 3 or temp < -3) {
setverdict(fail, "number of packets not within normal parameters (" & int2str(temp) & ")");
mtc.stop;
}
f_rtpem_stats_err_check(stats_rtp);
f_osmuxem_stats_err_check(stats_osmux);
/* Tear down */
f_flow_delete(RTPEM[0]);
f_flow_delete_osmux(OsmuxEM, ep, call_id);
setverdict(pass);
}
/* create one RTP and one OSmux emulations and pass data in both
directions. Create CRCX with wildcard Osmux CID and set it later
during MDCX. This is similar to how MSC sets up the call in AoIP. */
testcase TC_two_crcx_mdcx_and_rtp_osmux_wildcard() runs on dummy_CT {
f_two_crcx_mdcx_and_rtp_osmux(true);
}
/* create one RTP and one OSmux emulations and pass data in both
directions. Create CRCX with fixed Osmux CID and keep it during
MDCX. This is similar to how BSC sets up the call in AoIP. */
testcase TC_two_crcx_mdcx_and_rtp_osmux_fixed() runs on dummy_CT {
f_two_crcx_mdcx_and_rtp_osmux(false);
}
function f_crcx_and_dlcx_ep_callid_connid(MgcpEndpoint ep, MgcpCallId call_id) runs on dummy_CT {
var template MgcpCommand cmd;
var MgcpResponse resp;
@ -926,7 +1524,8 @@ module MGCP_Test {
portnr := omit
},
pt := pt,
codec := codec
codec := codec,
osmux_cid_sent := false
}
/* transmit RTP streams between two RTP Emulations back-to-back; expect no loss */
@ -1482,6 +2081,16 @@ module MGCP_Test {
execute(TC_crcx_and_dlcx_ep_callid_connid_inval());
execute(TC_crcx_and_dlcx_retrans());
execute(TC_crcx_osmux_wildcard());
execute(TC_crcx_osmux_fixed());
execute(TC_crcx_osmux_fixed_twice());
execute(TC_one_crcx_receive_only_osmux());
execute(TC_one_crcx_loopback_osmux());
execute(TC_two_crcx_and_rtp_osmux());
execute(TC_two_crcx_and_rtp_osmux_bidir());
execute(TC_two_crcx_mdcx_and_rtp_osmux_wildcard());
execute(TC_two_crcx_mdcx_and_rtp_osmux_fixed());
execute(TC_crcx_dlcx_30ep());
execute(TC_rtpem_selftest());

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<testsuite name='Titan' tests='35' failures='0' errors='0' skipped='1' inconc='0' time='MASKED'>
<testsuite name='Titan' tests='44' failures='0' errors='0' skipped='1' inconc='0' time='MASKED'>
<testcase classname='MGCP_Test' name='TC_selftest' time='MASKED'>
<skipped>no verdict</skipped>
</testcase>
@ -26,6 +26,15 @@
<testcase classname='MGCP_Test' name='TC_crcx_and_dlcx_ep_callid_inval' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_and_dlcx_ep_callid_connid_inval' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_and_dlcx_retrans' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_osmux_wildcard' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_osmux_fixed' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_osmux_fixed_twice' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_one_crcx_receive_only_osmux' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_one_crcx_loopback_osmux' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_rtp_osmux' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_and_rtp_osmux_bidir' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_mdcx_and_rtp_osmux_wildcard' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_two_crcx_mdcx_and_rtp_osmux_fixed' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_crcx_dlcx_30ep' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_rtpem_selftest' time='MASKED'/>
<testcase classname='MGCP_Test' name='TC_one_crcx_receive_only_rtp' time='MASKED'/>

View File

@ -41,6 +41,7 @@ DIR=../library
FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn MGCP_Types.ttcn MGCP_Templates.ttcn MGCP_CodecPort.ttcn
MGCP_CodecPort_CtrlFunct.ttcn MGCP_CodecPort_CtrlFunctDef.cc "
FILES+="RTP_CodecPort.ttcn RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_Emulation.ttcn IuUP_EncDec.cc "
FILES+="OSMUX_CodecPort.ttcn OSMUX_Emulation.ttcn OSMUX_Types.ttcn OSMUX_CodecPort_CtrlFunct.ttcn OSMUX_CodecPort_CtrlFunctDef.cc "
FILES+="Native_Functions.ttcn Native_FunctionDefs.cc IPCP_Types.ttcn "
FILES+="Osmocom_VTY_Functions.ttcn "
gen_links $DIR $FILES

View File

@ -1,5 +1,5 @@
#!/bin/sh
FILES="*.ttcn SDP_EncDec.cc *.c MGCP_CodecPort_CtrlFunctDef.cc IPL4asp_PT.cc IPL4asp_discovery.cc TCCConversion.cc TCCInterface.cc RTP_EncDec.cc RTP_CodecPort_CtrlFunctDef.cc IuUP_EncDec.cc Native_FunctionDefs.cc TELNETasp_PT.cc IP_EncDec.cc "
FILES="*.ttcn SDP_EncDec.cc *.c MGCP_CodecPort_CtrlFunctDef.cc IPL4asp_PT.cc IPL4asp_discovery.cc TCCConversion.cc TCCInterface.cc RTP_EncDec.cc RTP_CodecPort_CtrlFunctDef.cc OSMUX_CodecPort_CtrlFunctDef.cc IuUP_EncDec.cc Native_FunctionDefs.cc TELNETasp_PT.cc IP_EncDec.cc "
../regen-makefile.sh MGCP_Test.ttcn $FILES