From f7d9c0f22ba8d88171a242fcbe21187c3da5d76b Mon Sep 17 00:00:00 2001 From: Vadim Yanitskiy Date: Fri, 6 Sep 2019 00:08:17 +0200 Subject: [PATCH] Introduce PCUIF, BTS and ClckGen components for RAW PCU test cases The problem of existing test cases is that they mix IUT (i.e. OsmoPCU) with OsmoBTS (osmo-bts-virtual) and OsmocomBB (virt_phy). This approach allows to avoid dealing with TDMA clock indications and RTS requests on the PCU interface - this is done by OsmoBTS. On the other hand, our test scenarios may be potentially affected by undiscovered bugs in OsmoBTS and the virt_phy. In order to solve that problem, this change introduces a set of new components and the corresponding handler functions: - RAW_PCUIF_CT / f_PCUIF_CT_handler() - PCU interface (UNIX domain socket) handler. Creates a server listening for incoming connections on a given 'pcu_sock_path', handles connection establishment and message forwarding between connected BTS components (see below) and OsmoPCU. - RAW_PCU_BTS_CT / f_BTS_CT_handler() - represents a single BTS entity, connected to OsmoPCU through the RAW_PCUIF_CT. Takes care about sending System Information 13 to OsmoPCU, forwarding TDMA clock indications from a dedicated ClckGen component (see below), and filtering the received messages by the BTS number. Implements minimalistic scheduler for both DATA.ind and RTS.req messages, so they are send in accordance with the current TDMA frame number. - RAW_PCU_ClckGen_CT / f_ClckGen_CT_handler() - TDMA frame clock counter built on top of a timer. Sends clock indications to the BTS component. All components communicate using TTCN-3 ports and explicitly defined sets of messages (see RAW_PCU_MSG_PT). One noticeable kind of such messages is events (see RAW_PCU_Event and RAW_PCU_EventType). That's how e.g. the PCUIF component can notify the BTS component that OsmoPCU has just connected, or the BTS component can notify the MTC that SI13 negotiation is completed. Events may optionally have parameters (e.g. frame-number for TDMA_EV_*). Furthermore, the proposed set of components allows to have more than one BTS entity, so we can also test multi-BTS operation in the future. +-----+ +----------+ +---------+ | MTC +---------------+ PCUIF_CT +------+ OsmoPCU | +--+--+ +----+-----+ +---------+ | | | | | | | +-----------+ | +---------------+ +----+ BTS_CT #1 +------+ | ClckGen_CT #1 | | +-----+-----+ | +-------+-------+ | | | | | +---------------------------+ | | | +-----------+ | +---------------+ +----+ BTS_CT #2 +------+ | ClckGen_CT #2 | | +-----+-----+ | +-------+-------+ | | | | | +---------------------------+ | | | +-----------+ | +---------------+ +----+ BTS_CT #N +------+ | ClckGen_CT #N | +-----+-----+ +-------+-------+ | | +---------------------------+ Change-Id: I63a23abebab88fd5318eb4d907d6028e7c38e9a3 --- library/PCUIF_Types.ttcn | 9 + pcu/PCUIF_RAW_Components.ttcn | 393 ++++++++++++++++++++++++++++++++++ pcu/PCU_Tests_RAW.ttcn | 81 +++++++ 3 files changed, 483 insertions(+) create mode 100644 pcu/PCUIF_RAW_Components.ttcn diff --git a/library/PCUIF_Types.ttcn b/library/PCUIF_Types.ttcn index f13a7645c..bcd90666a 100644 --- a/library/PCUIF_Types.ttcn +++ b/library/PCUIF_Types.ttcn @@ -260,6 +260,15 @@ external function dec_PCUIF_Message(in octetstring stream) return PCUIF_Message with { extension "prototype(convert) decode(RAW)" }; +/* Generic template for matching messages by type and/or the BTS number */ +template PCUIF_Message tr_PCUIF_MSG(template PCUIF_MsgType msg_type := ?, + template uint8_t bts_nr := ?) := { + msg_type := msg_type, + bts_nr := bts_nr, + spare := ?, + u := ? +} + template (value) PCUIF_Message ts_PCUIF_RTS_REQ(template (value) uint8_t bts_nr, template (value) uint8_t trx_nr, template (value) uint8_t ts_nr, diff --git a/pcu/PCUIF_RAW_Components.ttcn b/pcu/PCUIF_RAW_Components.ttcn new file mode 100644 index 000000000..2297bbdd2 --- /dev/null +++ b/pcu/PCUIF_RAW_Components.ttcn @@ -0,0 +1,393 @@ +module PCUIF_RAW_Components { + +/* + * Components for (RAW) PCU test cases. + * + * (C) 2019 Vadim Yanitskiy + * + * 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 IPL4asp_Types all; +import from UD_Types all; + +import from PCUIF_Types all; +import from PCUIF_CodecPort all; + +/* Component communication diagram: + * + * +-----+ +----------+ +---------+ + * | MTC +---------------+ PCUIF_CT +------+ OsmoPCU | + * +--+--+ +----+-----+ +---------+ + * | | + * | | + * | | + * | +-----------+ | +---------------+ + * +----+ BTS_CT #1 +------+ | ClckGen_CT #1 | + * | +-----+-----+ | +-------+-------+ + * | | | | + * | +---------------------------+ + * | | + * | +-----------+ | +---------------+ + * +----+ BTS_CT #2 +------+ | ClckGen_CT #2 | + * | +-----+-----+ | +-------+-------+ + * | | | | + * | +---------------------------+ + * | | + * | +-----------+ | +---------------+ + * +----+ BTS_CT #N +------+ | ClckGen_CT #N | + * +-----+-----+ +-------+-------+ + * | | + * +---------------------------+ + */ + +/* Events are used by the components to indicate that something + * has happened, e.g. we have got a connection from the PCU. */ +type enumerated RAW_PCU_EventType { + /* Events related to RAW_PCUIF_CT */ + PCU_EV_DISCONNECT, /*!< OsmoPCU has disconnected */ + PCU_EV_CONNECT, /*!< OsmoPCU is now connected */ + + /* Events related to RAW_PCU_BTS_CT */ + BTS_EV_SI13_NEGO, /*!< SI13 negotiation complete */ + + /* TDMA clock related events (TDMA frame-number in parameters) */ + TDMA_EV_PDTCH_BLOCK_BEG, /*!< 1/4 bursts of a PDTCH block on both Uplink and Downlink */ + TDMA_EV_PDTCH_BLOCK_END, /*!< 4/4 bursts of a PDTCH block on both Uplink and Downlink */ + TDMA_EV_PTCCH_DL_BLOCK, /*!< 4/4 bursts of a PTCCH block on Downlink */ + TDMA_EV_PTCCH_UL_BURST, /*!< One Access Burst on PTCCH/U */ + + TDMA_EV_PDTCH_BLOCK_SENT /*!< A PDTCH block has been sent to the PCU */ +}; + +/* Union of all possible parameters of the events */ +type union RAW_PCU_EventParam { + integer tdma_fn +}; + +type record RAW_PCU_Event { + RAW_PCU_EventType event, + /* TODO: can we use 'anytype' here? */ + RAW_PCU_EventParam data optional +}; + +template (value) RAW_PCU_Event ts_RAW_PCU_EV(RAW_PCU_EventType event) := { + event := event, + data := omit +} +template RAW_PCU_Event tr_RAW_PCU_EV(template RAW_PCU_EventType event := ?, + template RAW_PCU_EventParam data := *) := { + event := event, + data := data +} + +template (value) RAW_PCU_Event ts_RAW_PCU_CLCK_EV(RAW_PCU_EventType event, integer fn) := { + event := event, + data := { tdma_fn := fn } +} +template RAW_PCU_Event tr_RAW_PCU_CLCK_EV := { + event := (TDMA_EV_PDTCH_BLOCK_BEG, TDMA_EV_PDTCH_BLOCK_END, + TDMA_EV_PTCCH_DL_BLOCK, TDMA_EV_PTCCH_UL_BURST, + TDMA_EV_PDTCH_BLOCK_SENT), + data := { tdma_fn := ? } +} + +/* Generic port for messages and events */ +type port RAW_PCU_MSG_PT message { + inout RAW_PCU_Event; + inout PCUIF_Message; +} with { extension "internal" }; + +/* TDMA frame clock generator */ +type component RAW_PCU_ClckGen_CT { + /* One TDMA frame is 4.615 ms long */ + timer T_TDMAClock := 4.615 / 1000.0; + port RAW_PCU_MSG_PT CLCK; + var integer fn := 0; +} + +function f_ClckGen_CT_handler() +runs on RAW_PCU_ClckGen_CT { + var integer fn104, fn52, fn13; + + while (true) { + fn104 := fn mod 104; + fn52 := fn mod 52; + fn13 := fn mod 13; + + if (fn13 == 0 or fn13 == 4 or fn13 == 8) { + /* 1/4 bursts of a PDTCH block on both Uplink and Downlink */ + CLCK.send(ts_RAW_PCU_CLCK_EV(TDMA_EV_PDTCH_BLOCK_BEG, fn)); + } else if (fn13 == 3 or fn13 == 7 or fn13 == 11) { + /* 4/4 bursts of a PDTCH block on both Uplink and Downlink */ + CLCK.send(ts_RAW_PCU_CLCK_EV(TDMA_EV_PDTCH_BLOCK_END, fn)); + } else if (fn104 == 90) { + /* 4/4 bursts of a PTCCH (Timing Advance Control) block on Downlink */ + CLCK.send(ts_RAW_PCU_CLCK_EV(TDMA_EV_PTCCH_DL_BLOCK, fn)); + } else if (fn52 == 12 or fn52 == 38) { + /* One Access Burst on PTCCH/U */ + CLCK.send(ts_RAW_PCU_CLCK_EV(TDMA_EV_PTCCH_UL_BURST, fn)); + } + + /* TDMA hyperframe period is (2048 * 51 * 26) frames */ + fn := (fn + 1) mod (2048 * 51 * 26); + + /* (Re)start TDMA clock timer and wait */ + T_TDMAClock.start; + T_TDMAClock.timeout; + } +} + +type record of PCUIF_Message PCUIF_MsgQueue; + +/* Enqueue a given message to the end of a given queue */ +private function f_PCUIF_MsgQueue_enqueue(inout PCUIF_MsgQueue queue, + in PCUIF_Message msg) +{ + queue := queue & { msg }; +} + +/* Dequeue the first message of a given queue */ +private function f_PCUIF_MsgQueue_dequeue(inout PCUIF_MsgQueue queue, + out PCUIF_Message msg) +{ + var integer len := lengthof(queue); + + if (len == 0) { + setverdict(fail, "Failed to dequeue a message: the queue is empty!"); + mtc.stop; + } + + /* Store the first message */ + msg := queue[0]; + + /* Remove the first message from queue */ + if (len > 1) { + queue := substr(queue, 1, len - 1); + } else { + queue := { }; + } +} + +/* Multiple base stations can be connected to the PCU. This component + * represents one BTS with an associated TDMA clock generator. */ +type component RAW_PCU_BTS_CT { + /* TDMA clock generator */ + var RAW_PCU_ClckGen_CT vc_CLCK_GEN; + port RAW_PCU_MSG_PT CLCK; + + /* Queues of PCUIF messages to be sent + * TODO: we may have multiple PDCH time-slots */ + var PCUIF_MsgQueue pdtch_data_queue := { }; + var PCUIF_MsgQueue pdtch_rts_queue := { }; + var PCUIF_MsgQueue ptcch_rts_queue := { }; + + /* Connection towards the PCU interface */ + port RAW_PCU_MSG_PT PCUIF; + /* Connection towards the test case */ + port RAW_PCU_MSG_PT TC; +} + +private altstep as_BTS_CT_MsgQueue(integer bts_nr) +runs on RAW_PCU_BTS_CT { + var PCUIF_Message pcu_msg; + + /* Enqueue DATA.ind and RTS.req messages */ + [] TC.receive(tr_PCUIF_MSG(PCU_IF_MSG_DATA_IND, bts_nr)) -> value pcu_msg { + f_PCUIF_MsgQueue_enqueue(pdtch_data_queue, pcu_msg); + repeat; + } + [] TC.receive(tr_PCUIF_RTS_REQ(bts_nr, sapi := PCU_IF_SAPI_PDTCH)) -> value pcu_msg { + f_PCUIF_MsgQueue_enqueue(pdtch_rts_queue, pcu_msg); + repeat; + } + [] TC.receive(tr_PCUIF_RTS_REQ(bts_nr, sapi := PCU_IF_SAPI_PTCCH)) -> value pcu_msg { + f_PCUIF_MsgQueue_enqueue(ptcch_rts_queue, pcu_msg); + repeat; + } + /* Forward other messages directly to the PCU */ + [] TC.receive(tr_PCUIF_MSG(?, bts_nr)) -> value pcu_msg { + PCUIF.send(pcu_msg); + repeat; + } +} + +private altstep as_BTS_CT_TDMASched(integer bts_nr) +runs on RAW_PCU_BTS_CT { + var PCUIF_Message pcu_msg; + var RAW_PCU_Event event; + + [] CLCK.receive(tr_RAW_PCU_EV(TDMA_EV_PDTCH_BLOCK_BEG)) -> value event { + /* If the RTS queue for PDTCH is not empty, send a message */ + if (lengthof(pdtch_rts_queue) > 0) { + f_PCUIF_MsgQueue_dequeue(pdtch_rts_queue, pcu_msg); + + /* Patch TDMA frame / block number and send */ + pcu_msg.u.rts_req.fn := event.data.tdma_fn; + pcu_msg.u.rts_req.block_nr := 0; /* FIXME! */ + PCUIF.send(pcu_msg); + } + + /* We don't really need to send every frame to OsmoPCU, because + * it omits frame numbers not starting at a MAC block. */ + PCUIF.send(ts_PCUIF_TIME_IND(bts_nr, event.data.tdma_fn)); + repeat; + } + [lengthof(pdtch_data_queue) > 0] CLCK.receive(tr_RAW_PCU_EV(TDMA_EV_PDTCH_BLOCK_END)) -> value event { + /* Dequeue a DATA.ind message */ + f_PCUIF_MsgQueue_dequeue(pdtch_data_queue, pcu_msg); + + /* Patch TDMA frame / block number */ + pcu_msg.u.data_ind.fn := event.data.tdma_fn; + pcu_msg.u.data_ind.block_nr := 0; /* FIXME! */ + + PCUIF.send(pcu_msg); /* Send to the PCU and notify the TC */ + TC.send(ts_RAW_PCU_CLCK_EV(TDMA_EV_PDTCH_BLOCK_SENT, event.data.tdma_fn)); + repeat; + } + [lengthof(ptcch_rts_queue) > 0] CLCK.receive(tr_RAW_PCU_EV(TDMA_EV_PTCCH_DL_BLOCK)) -> value event { + /* Dequeue an RTS.req message for PTCCH */ + f_PCUIF_MsgQueue_dequeue(ptcch_rts_queue, pcu_msg); + + /* Patch TDMA frame / block number and send */ + pcu_msg.u.rts_req.fn := event.data.tdma_fn; + pcu_msg.u.rts_req.block_nr := 0; /* FIXME! */ + PCUIF.send(pcu_msg); + repeat; + } + /* Ignore other clock events (and guard against an empty queue) */ + [] CLCK.receive(tr_RAW_PCU_CLCK_EV) { repeat; } +} + +function f_BTS_CT_handler(integer bts_nr, PCUIF_info_ind info_ind) +runs on RAW_PCU_BTS_CT { + var PCUIF_Message pcu_msg; + var RAW_PCU_Event event; + + /* Init TDMA clock generator (so we can stop and start it) */ + vc_CLCK_GEN := RAW_PCU_ClckGen_CT.create("ClckGen-" & int2str(bts_nr)) alive; + connect(vc_CLCK_GEN:CLCK, self:CLCK); + + /* Wait until the PCU is connected */ + PCUIF.receive(tr_RAW_PCU_EV(PCU_EV_CONNECT)); + + /* TODO: implement ACT.req handling */ + alt { + /* Wait for TXT.ind (PCU_VERSION) and respond with INFO.ind (SI13) */ + [] PCUIF.receive(tr_PCUIF_TXT_IND(bts_nr, PCU_VERSION, ?)) -> value pcu_msg { + log("Rx TXT.ind from the PCU, version is ", pcu_msg.u.txt_ind.text); + + /* Send System Information 13 to the PCU */ + PCUIF.send(PCUIF_Message:{ + msg_type := PCU_IF_MSG_INFO_IND, + bts_nr := bts_nr, + spare := '0000'O, + u := { info_ind := info_ind } + }); + + /* Notify the test case that we're done with SI13 */ + TC.send(ts_RAW_PCU_EV(BTS_EV_SI13_NEGO)); + + /* Start feeding clock to the PCU */ + vc_CLCK_GEN.start(f_ClckGen_CT_handler()); + repeat; + } + /* PCU -> test case forwarding (filter by the BTS number) */ + [] PCUIF.receive(tr_PCUIF_MSG(?, bts_nr)) -> value pcu_msg { + TC.send(pcu_msg); + repeat; + } + + /* TC -> [Queue] -> PCU forwarding */ + [] as_BTS_CT_MsgQueue(bts_nr); + /* TDMA scheduler (clock and queue handling) */ + [] as_BTS_CT_TDMASched(bts_nr); + + /* TODO: handle events (e.g. disconnection) from the PCU interface */ + [] PCUIF.receive(tr_RAW_PCU_EV) -> value event { + log("Ignore unhandled event: ", event); + repeat; + } + } +} + +/* PCU interface (UNIX domain socket) handler */ +type component RAW_PCUIF_CT { + var ConnectionId g_pcu_conn_id := -1; + var boolean g_pcu_connected := false; + port PCUIF_CODEC_PT PCU; + + /* Connection towards BTS component(s) */ + port RAW_PCU_MSG_PT BTS; + /* Connection to the MTC (test case) */ + port RAW_PCU_MSG_PT MTC; +}; + +/* All received messages and events from OsmoPCU can be additionally sent + * directly to the MTC component. Pass direct := true to enable this mode. */ +function f_PCUIF_CT_handler(charstring pcu_sock_path, boolean direct := false) +runs on RAW_PCUIF_CT { + var PCUIF_send_data pcu_sd_msg; + var PCUIF_Message pcu_msg; + timer T_Conn := 10.0; + + log("Init PCU interface on '" & pcu_sock_path & "', waiting for connection..."); + g_pcu_conn_id := f_pcuif_listen(PCU, pcu_sock_path); + + /* Wait for connection */ + T_Conn.start; + alt { + [not g_pcu_connected] PCU.receive(UD_connected:?) { + log("OsmoPCU is now connected"); + /* Duplicate this event to the MTC if requested */ + if (direct) { MTC.send(ts_RAW_PCU_EV(PCU_EV_CONNECT)); } + BTS.send(ts_RAW_PCU_EV(PCU_EV_CONNECT)); + g_pcu_connected := true; + setverdict(pass); + T_Conn.stop; + repeat; + } + /* PCU -> BTS / MTC message forwarding */ + [g_pcu_connected] PCU.receive(PCUIF_send_data:?) -> value pcu_sd_msg { + /* Send to the BTSes if at least one is connected */ + if (BTS.checkstate("Connected")) { BTS.send(pcu_sd_msg.data); } + /* Duplicate this message to the MTC if requested */ + if (direct) { MTC.send(pcu_sd_msg.data); } + repeat; + } + /* MTC -> PCU message forwarding */ + [g_pcu_connected] MTC.receive(PCUIF_Message:?) -> value pcu_msg { + PCU.send(t_SD_PCUIF(g_pcu_conn_id, pcu_msg)); + repeat; + } + /* BTS -> PCU message forwarding */ + [g_pcu_connected] BTS.receive(PCUIF_Message:?) -> value pcu_msg { + PCU.send(t_SD_PCUIF(g_pcu_conn_id, pcu_msg)); + repeat; + } + [not g_pcu_connected] MTC.receive(PCUIF_Message:?) -> value pcu_msg { + log("PCU is not connected, dropping ", pcu_msg); + repeat; + } + [not g_pcu_connected] BTS.receive(PCUIF_Message:?) -> value pcu_msg { + log("PCU is not connected, dropping ", pcu_msg); + repeat; + } + /* TODO: handle disconnect and reconnect of the PCU */ + [] PCU.receive { + log("Unhandled message on PCU interface"); + repeat; + } + [not g_pcu_connected] T_Conn.timeout { + setverdict(fail, "Timeout waiting for PCU connection"); + mtc.stop; + } + } +} + +} diff --git a/pcu/PCU_Tests_RAW.ttcn b/pcu/PCU_Tests_RAW.ttcn index 4732da60a..8424df323 100644 --- a/pcu/PCU_Tests_RAW.ttcn +++ b/pcu/PCU_Tests_RAW.ttcn @@ -30,6 +30,7 @@ import from NS_Emulation all; /* NSConfiguration */ import from UD_Types all; import from PCUIF_Types all; import from PCUIF_CodecPort all; +import from PCUIF_RAW_Components all; import from IPL4asp_Types all; import from NS_CodecPort all; import from NS_CodecPort_CtrlFunct all; @@ -438,6 +439,86 @@ testcase TC_pcuif_suspend() runs on bssgp_pcuif_CT { setverdict(pass); } +type component RAW_PCU_Test_CT extends bssgp_CT { + /* Connection to the BTS component (one for now) */ + port RAW_PCU_MSG_PT BTS; + /* Connection to the PCUIF component */ + port RAW_PCU_MSG_PT PCUIF; + + /* Guard timeout */ + timer g_T_guard := 60.0; +}; + +private altstep as_Tguard_RAW() runs on RAW_PCU_Test_CT { + [] g_T_guard.timeout { + setverdict(fail, "Timeout of T_guard"); + mtc.stop; + } +} + +private function f_init_raw(charstring id) +runs on RAW_PCU_Test_CT { + var PCUIF_info_ind info_ind; + var RAW_PCUIF_CT vc_PCUIF; + var RAW_PCU_BTS_CT vc_BTS; + + /* Start the guard timer */ + g_T_guard.start; + activate(as_Tguard_RAW()); + + /* Init PCU interface component */ + vc_PCUIF := RAW_PCUIF_CT.create("PCUIF-" & id); + connect(vc_PCUIF:MTC, self:PCUIF); + map(vc_PCUIF:PCU, system:PCU); + + /* Create one BTS component (we may want more some day) */ + vc_BTS := RAW_PCU_BTS_CT.create("BTS-" & id); + connect(vc_BTS:PCUIF, vc_PCUIF:BTS); + connect(vc_BTS:TC, self:BTS); + + /* FIXME: make sure to use parameters from mp_gb_cfg.cell_id in the PCU INFO IND */ + info_ind := { + version := PCU_IF_VERSION, + flags := c_PCUIF_Flags_default, + trx := valueof(ts_PCUIF_InfoTrxs_def), + /* TODO: make this configurable */ + bsic := 7, mcc := 262, mnc := 42, + mnc_3_digits := 0, + /* TODO: make this configurable */ + lac := 13135, rac := 0, + nsei := mp_nsconfig.nsei, + nse_timer := { 3, 3, 3, 3, 30, 3, 10 }, + cell_timer := { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }, + cell_id := 20960, + repeat_time := 5 * 50, + repeat_count := 3, + bvci := mp_gb_cfg.bvci, + t3142 := 20, + t3169 := 5, + t3191 := 5, + t3193_10ms := 160, + t3195 := 5, + t3101 := 10, + t3103 := 4, + t3105 := 8, + cv_countdown := 15, + dl_tbf_ext := 250 * 10, /* ms */ + ul_tbf_ext := 250 * 10, /* ms */ + initial_cs := 2, + initial_mcs := 6, + nsvci := { mp_nsconfig.nsvci, 0 }, + local_pprt := { mp_nsconfig.remote_udp_port, 0 }, + remote_port := { mp_nsconfig.local_udp_port, 0 }, + remote_ip := { f_inet_haddr(mp_nsconfig.local_ip), '00000000'O } + }; + + vc_PCUIF.start(f_PCUIF_CT_handler(mp_pcu_sock_path)); + vc_BTS.start(f_BTS_CT_handler(0, info_ind)); + + /* Wait until the BTS is ready (SI13 negotiated) */ + BTS.receive(tr_RAW_PCU_EV(BTS_EV_SI13_NEGO)); +} + control { execute( TC_ns_reset() );