| module BTS_Tests_SMSCB { |
| |
| /* Integration Tests for OsmoBTS |
| * (C) 2019 by Harald Welte <laforge@gnumonks.org> |
| * 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 |
| * |
| * This test suite tests the SMSCB (Cell Broadcast) related functionality of |
| * OsmoBTS by attaching to the A-bis RSL and Um interface and emulating both |
| * BSC and MS. |
| */ |
| |
| import from Misc_Helpers all; |
| import from General_Types all; |
| import from Osmocom_Types all; |
| import from GSM_Types all; |
| import from L1CTL_PortType all; |
| import from L1CTL_Types all; |
| import from LAPDm_Types all; |
| import from IPA_Emulation all; |
| import from GSM_RR_Types all; |
| import from L3_Templates all; |
| |
| import from RSL_Types all; |
| |
| import from PCUIF_Types all; |
| import from PCUIF_CodecPort all; |
| |
| import from Osmocom_VTY_Functions all; |
| |
| import from BTS_Tests all; |
| |
| /*********************************************************************** |
| * Cell Broadcast related tests |
| ***********************************************************************/ |
| |
| /* Test parameters for one channel (e.g. Basic, Extended) */ |
| type record CbchTestParsChan { |
| /* list of "normal" (sent-once) SMSCB messages */ |
| CbchTestMsgs msgs, |
| /* default message, if any */ |
| CbchTestMsg default_msg optional |
| } |
| |
| private template (value) CbchTestParsChan |
| t_CbchPC(template (value) CbchTestMsgs msgs, template (omit) CbchTestMsg def := omit) := { |
| msgs := msgs, |
| default_msg := def |
| } |
| |
| /* CBCH test parameters for most of our tests */ |
| type record CbchTestPars { |
| /* Should we execute on SDCCH4 or SDCCH8? */ |
| RslChannelNr chan_nr, |
| /* Frequency Hopping parameters */ |
| FreqHopPars fhp, |
| /* Parameters for BASIC CBCH */ |
| CbchTestParsChan basic, |
| /* Parameters for EXTENDED CBCH */ |
| CbchTestParsChan extended optional |
| }; |
| |
| type record CbchTestMsg { |
| /* config / input data */ |
| RSL_CbCommand rsl_cb_cmd, |
| uint2_t last_block, /* 0..3 */ |
| octetstring payload, |
| /* computed / result data */ |
| CbchBlocks blocks optional |
| }; |
| type record of CbchTestMsg CbchTestMsgs; |
| |
| /* a single 22byte block within a CbchTestMsg */ |
| type record CbchBlock { |
| uint4_t seq_nr, /* as per TS 04.12 */ |
| boolean is_last, |
| OCT22 payload, |
| boolean seen_once |
| }; |
| type record of CbchBlock CbchBlocks; |
| |
| /* compute the expected blocks for given test parameters */ |
| private function f_cbch_compute_exp_blocks(inout CbchTestPars pars) { |
| f_cbch_compute_exp_blocks_chan(pars.basic); |
| if (ispresent(pars.extended)) { |
| f_cbch_compute_exp_blocks_chan(pars.extended); |
| } |
| } |
| private function f_cbch_compute_exp_blocks_chan(inout CbchTestParsChan pars_chan) { |
| var integer i; |
| for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { |
| pars_chan.msgs[i].blocks := f_comp_blocks(pars_chan.msgs[i]); |
| } |
| if (ispresent(pars_chan.default_msg)) { |
| pars_chan.default_msg.blocks := f_comp_blocks(pars_chan.default_msg); |
| } |
| } |
| private function f_comp_blocks(in CbchTestMsg msg) return CbchBlocks { |
| var CbchBlocks blocks := {}; |
| var integer i; |
| |
| for (i := 0; i <= msg.last_block; i := i+1) { |
| var CbchBlock block := { |
| seq_nr := i, |
| is_last := false, |
| payload := substr(msg.payload, 22*i, 22), |
| seen_once := false |
| }; |
| if (msg.rsl_cb_cmd == RSL_CB_CMD_SCHEDULE and i == 0) { |
| block.seq_nr := 8; |
| } |
| if (i == msg.last_block) { |
| block.is_last := true; |
| } |
| blocks := blocks & {block}; |
| } |
| |
| return blocks; |
| }; |
| |
| /* TS 48.058 Section 9.3.41 */ |
| private function f_cbch_block_nr2rsl(uint2_t nr) return uint2_t { |
| select (nr) { |
| case (0) { return 1; } |
| case (1) { return 2; } |
| case (2) { return 3; } |
| case (3) { return 0; } |
| } |
| setverdict(fail, "Invalid block number"); |
| mtc.stop; |
| } |
| |
| private function f_cbch_fn2tb(uint32_t fn) return integer |
| { |
| return (fn/51) mod 8; /* TS 05.02 Section 6.5.4 */ |
| } |
| |
| /* Verify the CBCH TB scheduling rules of TS 05.02 Section 6.5.4 */ |
| private function f_cbch_fn_verify(uint32_t fn, CBCH_Block cb) |
| { |
| var integer tb := f_cbch_fn2tb(fn); |
| if (cb.block_type.seq_nr == 15 /* null */) { |
| /* always permitted */ |
| return; |
| } else if (cb.block_type.seq_nr == 8 /* schedule */) { |
| if (tb != 0) { |
| setverdict(fail, "Schedule block at TB=", tb); |
| } |
| } else if (cb.block_type.seq_nr < 4) { |
| if (cb.block_type.seq_nr != tb and cb.block_type.seq_nr+4 != tb) { |
| setverdict(fail, "Normal block at wrong TB=", tb, ": ", cb); |
| } |
| } |
| } |
| |
| private function f_rsl_smscb_default_null() runs on test_CT |
| { |
| var RSL_IE_CbCommandType cmd_type := |
| valueof(ts_RSL_IE_CbCmdType(RSL_CB_CMD_DEFAULT, 1, true)); |
| RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_SMSCB_CMD(cmd_type, ''O))); |
| } |
| |
| template RSL_IE t_RSL_IE_SMSCB_EXT := t_RSL_IE(RSL_IE_SMSCB_CHAN_INDICATOR, {smscb_chan_ind := 1}); |
| |
| private function f_smscb_setup_rsl_chan(inout CbchTestParsChan pars_chan, boolean extd := false) |
| runs on test_CT { |
| var integer i; |
| var CbchTestMsg msg; |
| var uint2_t rsl_last_block; |
| var RSL_IE_CbCommandType cmd_type; |
| var RSL_Message rsl; |
| |
| /* send SMSCB[s] via RSL */ |
| for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { |
| msg := pars_chan.msgs[i]; |
| rsl_last_block := f_cbch_block_nr2rsl(msg.last_block); |
| cmd_type := valueof(ts_RSL_IE_CbCmdType(msg.rsl_cb_cmd, rsl_last_block)); |
| rsl := valueof(ts_RSL_SMSCB_CMD(cmd_type, msg.payload)); |
| if (extd) { |
| rsl.ies := rsl.ies & { valueof(t_RSL_IE_SMSCB_EXT) }; |
| } |
| RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); |
| } |
| if (ispresent(pars_chan.default_msg)) { |
| msg := pars_chan.default_msg; |
| rsl_last_block := f_cbch_block_nr2rsl(msg.last_block); |
| cmd_type := valueof(ts_RSL_IE_CbCmdType(msg.rsl_cb_cmd, rsl_last_block, false)); |
| rsl := valueof(ts_RSL_SMSCB_CMD(cmd_type, msg.payload)); |
| if (extd) { |
| rsl.ies := rsl.ies & { valueof(t_RSL_IE_SMSCB_EXT) }; |
| } |
| RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); |
| } |
| } |
| |
| private function f_vty_cbch_setup(in RslChannelNr chan_nr) runs on test_CT { |
| |
| if (match(chan_nr, t_RslChanNr_CBCH4(0))) { |
| f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot 0"}, |
| "phys_chan_config CCCH+SDCCH4+CBCH"); |
| /* (Re)configure timeslots 1..3 as TCH/F */ |
| for (var integer tn := 1; tn <= 3; tn := tn + 1) { |
| f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", |
| "timeslot " & int2str(tn) }, |
| "phys_chan_config TCH/F"); |
| } |
| } else if (match(chan_nr, t_RslChanNr_CBCH8(?))) { |
| f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot 0"}, |
| "phys_chan_config CCCH+SDCCH4"); |
| f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", |
| "timeslot " & int2str(chan_nr.tn) }, |
| "phys_chan_config SDCCH8+CBCH"); |
| |
| /* (Re)configure timeslots 1..3 (excluding the given one) as TCH/F */ |
| for (var integer tn := 1; tn <= 3; tn := tn + 1) { |
| if (tn == chan_nr.tn) |
| { continue; } |
| f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", |
| "timeslot " & int2str(tn) }, |
| "phys_chan_config TCH/F"); |
| } |
| } |
| f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); |
| f_sleep(2.0); |
| } |
| private function f_smscb_setup(inout CbchTestPars pars) runs on test_CT { |
| /* Make sure we've got either SDCCH4+CBCH or SDCCH8+CBCH. |
| * SDCCH4+CBCH can only be allocated on TS0, SDCCH8+CBCH on TS0..3. |
| * On C0 the first timeslot shall always transmit BCCH, thus TS1..3.*/ |
| if (not match(pars.chan_nr, (t_RslChanNr_CBCH4(0), t_RslChanNr_CBCH8(1), |
| t_RslChanNr_CBCH8(2), t_RslChanNr_CBCH8(3)))) { |
| setverdict(inconc, "Unhandled channel number: ", pars.chan_nr); |
| mtc.stop; |
| } |
| |
| /* Obtain frequency hopping parameters for a given timeslot */ |
| if (mp_freq_hop_enabled and mp_transceiver_num > 1) |
| { f_resolve_fh_params(pars.fhp, pars.chan_nr.tn); } |
| else |
| { pars.fhp.enabled := false; } |
| |
| f_cbch_compute_exp_blocks(pars); |
| |
| f_init_vty_bsc(); |
| /* ensure that a CBCH is present in channel combination */ |
| f_vty_cbch_setup(pars.chan_nr); |
| f_init(); |
| |
| f_init_l1ctl(); |
| |
| /* Tune L1 to the given CBCH timeslot (SDCCH4+CBCH or SDCCH8+CBCH) */ |
| if (match(pars.chan_nr, t_RslChanNr_CBCH4(0))) { |
| f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); |
| } else { |
| f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED); |
| |
| if (pars.fhp.enabled) { |
| L1CTL.send(ts_L1CTL_DM_EST_REQ_H1(pars.chan_nr, mp_tsc_def, |
| pars.fhp.maio_hsn.hsn, |
| pars.fhp.maio_hsn.maio, |
| pars.fhp.ma)); |
| } else { |
| L1CTL.send(ts_L1CTL_DM_EST_REQ_H0(pars.chan_nr, mp_tsc_def, |
| mp_trx_pars[0].arfcn)); |
| } |
| } |
| |
| /* send SMSCB[s] via RSL */ |
| f_smscb_setup_rsl_chan(pars.basic, false); |
| if (ispresent(pars.extended)) { |
| f_smscb_setup_rsl_chan(pars.extended, true); |
| } |
| } |
| |
| /* construct a receive/match template for given block_nr in given msg */ |
| private function f_get_block_template(CbchTestMsg msg, integer block_nr) return template CBCH_Block { |
| var template CBCH_Block tr; |
| if (block_nr < lengthof(msg.blocks)) { |
| var CbchBlock b := msg.blocks[block_nr]; |
| tr := tr_CBCH_Block(b.seq_nr, b.is_last, b.payload); |
| } else { |
| tr := tr_CBCH_Block(15, ?, ?); |
| } |
| return tr; |
| } |
| |
| /* the heart of the CBCH test case matching engine for one channel (basic, extended) */ |
| private function f_cbch_match(inout CbchTestParsChan pars_chan, CBCH_Block cb, integer tb) |
| { |
| var integer block_nr := tb mod 4; |
| var integer i; |
| |
| if (not match(cb, tr_CBCH_Block)) { |
| setverdict(fail, "Illegal CBCH Block received: ", cb); |
| } else { |
| var boolean matched := false; |
| /* check if it's any of our expected blocks */ |
| for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { |
| if (block_nr < lengthof(pars_chan.msgs[i].blocks)) { |
| if (match(cb, f_get_block_template(pars_chan.msgs[i], block_nr))) { |
| log("Found block_nr ", block_nr, " of msg ", i); |
| if (not pars_chan.msgs[i].blocks[block_nr].seen_once) { |
| pars_chan.msgs[i].blocks[block_nr].seen_once := true; |
| setverdict(pass); |
| } else { |
| setverdict(fail, "Received SMSCB twice! ", cb); |
| } |
| matched := true; |
| break; |
| } |
| } |
| } |
| if (not matched) { |
| var template CBCH_Block tr; |
| if (ispresent(pars_chan.default_msg)) { |
| /* it must be a block of the default message */ |
| tr := f_get_block_template(pars_chan.default_msg, block_nr); |
| } else { |
| /* it must be a NULL block */ |
| tr := tr_CBCH_Block(15, ?, ?); |
| } |
| if (not match(cb, tr)) { |
| setverdict(fail, "Received unexpected CBCH block: ", cb); |
| } else { |
| log("Found block_nr ", block_nr, " of DEFAULT/NULL"); |
| if (ispresent(pars_chan.default_msg) and |
| block_nr < lengthof(pars_chan.default_msg.blocks)) { |
| pars_chan.default_msg.blocks[block_nr].seen_once := true; |
| } |
| } |
| } |
| } |
| } |
| |
| /* Report/Evaluate the per-channel CBCH test results */ |
| private function f_cbch_report(CbchTestParsChan pars_chan, charstring id) |
| { |
| var integer i, j; |
| |
| /* verify that each block of each message has been seen once */ |
| for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { |
| for (j := 0; j < lengthof(pars_chan.msgs[i].blocks); j := j+1) { |
| var CbchBlock b := pars_chan.msgs[i].blocks[j]; |
| if (not b.seen_once) { |
| setverdict(fail, "Timeout waiting for ", id, " CBCH block ", |
| j, " of msg ", i); |
| } |
| } |
| } |
| if (ispresent(pars_chan.default_msg)) { |
| /* verify that each block of default message has been seen at least once */ |
| for (j := 0; j < lengthof(pars_chan.default_msg.blocks); j := j+1) { |
| var CbchBlock b := pars_chan.default_msg.blocks[j]; |
| if (not b.seen_once) { |
| setverdict(fail, "Timeout waiting for at leaset one instance of ", |
| "CBCH block ", j, " of DEFAULT msg"); |
| } |
| } |
| } |
| } |
| |
| /* shared function doing the heavy lifting for most CBCH tests */ |
| private function f_TC_smscb(CbchTestPars pars) runs on test_CT { |
| var L1ctlDlMessage dl; |
| var integer msg_count; |
| timer T; |
| |
| msg_count := lengthof(pars.basic.msgs); |
| if (ispresent(pars.basic.default_msg)) { |
| msg_count := msg_count + 1; |
| } |
| if (ispresent(pars.extended)) { |
| msg_count := msg_count + lengthof(pars.extended.msgs); |
| if (ispresent(pars.extended.default_msg)) { |
| msg_count := msg_count + 1; |
| } |
| } |
| |
| f_smscb_setup(pars); |
| |
| /* dynamically adjust timeout based on number of messages */ |
| T.start(5.0 + 3.0 * int2float(msg_count)); |
| /* Expect this to show up exactly once on the basic CBCH (four blocks) */ |
| alt { |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(pars.chan_nr)) -> value dl { |
| var integer tb := f_cbch_fn2tb(dl.dl_info.frame_nr); |
| var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); |
| log("Tb=", tb, ", CBCH: ", dl, ", block: ", cb); |
| |
| /* detect the proper CBCH messages; check frame number */ |
| f_cbch_fn_verify(dl.dl_info.frame_nr, cb); |
| |
| if (tb < 4) { |
| f_cbch_match(pars.basic, cb, tb); |
| } else { |
| if (not ispresent(pars.extended)) { |
| /* no parameters for ext. BCCH given: ignore */ |
| repeat; |
| } |
| f_cbch_match(pars.extended, cb, tb); |
| } |
| repeat; |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout { |
| f_cbch_report(pars.basic, "Basic"); |
| if (ispresent(pars.extended)) { |
| f_cbch_report(pars.extended, "Extended"); |
| } |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| |
| private function f_TC_smscb_default_only(CbchTestPars pars) runs on test_CT { |
| var L1ctlDlMessage dl; |
| timer T := 5.0; |
| |
| f_smscb_setup(pars); |
| |
| /* ensure whatever initial NULL messages have all been drained */ |
| f_sleep(5.0); |
| L1CTL.clear; |
| |
| T.start; |
| alt { |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(pars.chan_nr)) -> value dl { |
| var integer tb := f_cbch_fn2tb(dl.dl_info.frame_nr); |
| log("CBCH: ", dl); |
| var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); |
| /* detect the proper CBCH messages; check frame number */ |
| f_cbch_fn_verify(dl.dl_info.frame_nr, cb); |
| if (tb >= 4) { |
| /* skip extended CBCH for now */ |
| repeat; |
| } |
| if (not match(cb, tr_CBCH_Block)) { |
| setverdict(fail, "Illegal CBCH Block received: ", cb); |
| } else { |
| var uint4_t rx_seq_nr := cb.block_type.seq_nr; |
| var template CBCH_Block tr; |
| if (rx_seq_nr < lengthof(pars.basic.msgs[0].blocks)) { |
| var CbchBlock b := pars.basic.msgs[0].blocks[rx_seq_nr]; |
| tr := tr_CBCH_Block(b.seq_nr, b.is_last, b.payload); |
| } else { |
| tr := tr_CBCH_Block(15, ?, ?); |
| } |
| if (match(cb, tr)) { |
| setverdict(pass); /* FIXME: check that all blocks are received? */ |
| repeat; |
| } else { |
| setverdict(fail, "Unexpected CBCH block ", cb, ", expected ", tr); |
| } |
| } |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout {} |
| } |
| |
| /* don't shut down; some tests still want to continue */ |
| } |
| |
| private const CbchTestMsgs msgs_1m_1b_norm := { |
| { RSL_CB_CMD_NORMAL, 0, '001000320f1141660c344dd3cba09a0c000000000000'O, omit } |
| } |
| |
| private const CbchTestMsgs msgs_1m_2b_norm := { |
| { RSL_CB_CMD_NORMAL, 1, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O, |
| omit } |
| } |
| |
| private const CbchTestMsgs msgs_1m_3b_norm := { |
| { RSL_CB_CMD_NORMAL, 2, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O & |
| '101112131415161718191a1b1c1d1e1f202223242526'O, |
| omit } |
| } |
| |
| private const CbchTestMsgs msgs_1m_4b_norm := { |
| { RSL_CB_CMD_NORMAL, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O & |
| '101112131415161718191a1b1c1d1e1f202223242526'O & |
| '202122232425262728292a2b2c2d2e2f303233343536'O, |
| omit } |
| } |
| |
| private const CbchTestMsgs msgs_1m_4b_sched := { |
| { RSL_CB_CMD_SCHEDULE, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O & |
| '101112131415161718191a1b1c1d1e1f202223242526'O & |
| '202122232425262728292a2b2c2d2e2f303233343536'O, |
| omit } |
| } |
| |
| private const CbchTestMsgs msgs_3m_4b_norm := { |
| { RSL_CB_CMD_NORMAL, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O & |
| '101112131415161718191a1b1c1d1e1f202223242526'O & |
| '201122232425262728292a2b2c2d2e2f303233343536'O, |
| omit }, |
| { RSL_CB_CMD_NORMAL, 3, '002000320f1141660c344dd3cba09a0c000000000000'O & |
| '002102030405060708090a0b0c0d0e0f101213141516'O & |
| '102112131415161718191a1b1c1d1e1f202223242526'O & |
| '202122232425262728292a2b2c2d2e2f303233343536'O, |
| omit }, |
| { RSL_CB_CMD_NORMAL, 3, '003000320f1141660c344dd3cba09a0c000000000000'O & |
| '003102030405060708090a0b0c0d0e0f101213141516'O & |
| '103112131415161718191a1b1c1d1e1f202223242526'O & |
| '203122232425262728292a2b2c2d2e2f303233343536'O, |
| omit } |
| } |
| |
| private const CbchTestMsgs msgs_1m_3b_default := { |
| { RSL_CB_CMD_DEFAULT, 2, '001000320f1141660c344dd3cba09a0c000000000000'O & |
| '000102030405060708090a0b0c0d0e0f101213141516'O & |
| '101112131415161718191a1b1c1d1e1f202223242526'O, |
| omit } |
| } |
| private const CbchTestMsg msg_default := { |
| RSL_CB_CMD_DEFAULT, 0, '010203040506070708090a0b0c0d0e0f101112131415'O, |
| omit |
| } |
| |
| /* transmit single-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_1block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_1b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_1block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_1b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit dual-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_2block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_2b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_2block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_2b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit triple-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_3block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_3block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit quad-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_4block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_4b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_4block() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_4b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit multiple commands of each 4 blocks */ |
| testcase TC_sms_cb_cmd_sdcch4_multi() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_3m_4b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_multi() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_3m_4b_norm)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit multiple commands of each 4 blocks on CBCH EXTD */ |
| testcase TC_sms_cb_cmd_sdcch4_extd_multi() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC({})), |
| extended := valueof(t_CbchPC(msgs_3m_4b_norm)) |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_extd_multi() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC({})), |
| extended := valueof(t_CbchPC(msgs_3m_4b_norm)) |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit SMSCB COMMAND with SCHEDULE payload */ |
| testcase TC_sms_cb_cmd_sdcch4_schedule() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_4b_sched)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_schedule() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_4b_sched)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* set a DEFAULT message; verify it gets transmitted all the time */ |
| testcase TC_sms_cb_cmd_sdcch4_default_only() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_default)), |
| extended := omit |
| }; |
| f_TC_smscb_default_only(pars); |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_default_only() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_default)), |
| extended := omit |
| }; |
| f_TC_smscb_default_only(pars); |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| |
| testcase TC_sms_cb_cmd_sdcch4_default_and_normal() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm, msg_default)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_default_and_normal() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH8(2)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm, msg_default)), |
| extended := omit |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* first set a DEFAULT message, then disable it again */ |
| testcase TC_sms_cb_cmd_sdcch4_default_then_null() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_default)), |
| extended := omit |
| }; |
| var L1ctlDlMessage dl; |
| timer T := 5.0; |
| |
| f_TC_smscb_default_only(pars); |
| |
| /* disable DEFAULT message; switch back to NULL */ |
| f_rsl_smscb_default_null(); |
| |
| /* ensure whatever initial non-NULL messages have all been drained */ |
| f_sleep(5.0); |
| L1CTL.clear; |
| |
| T.start; |
| alt { |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_CBCH4(0))) -> value dl { |
| log("CBCH: ", dl); |
| var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); |
| /* detect the proper CBCH messages; check frame number */ |
| f_cbch_fn_verify(dl.dl_info.frame_nr, cb); |
| if (not match(cb, tr_CBCH_Block)) { |
| setverdict(fail, "Illegal CBCH Block received: ", cb); |
| } else { |
| if (not match(cb, tr_CBCH_Block(15, ?, ?))) { |
| setverdict(fail, "Unexpected non-NULL CBCH block received"); |
| } |
| repeat; |
| } |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout { |
| setverdict(pass); |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| |
| /* Verify there are no CBCH load indications if no CBCH is present */ |
| testcase TC_cbch_load_idle_no_cbch() runs on test_CT { |
| var ASP_RSL_Unitdata rx_ud; |
| timer T := 10.0; |
| |
| f_init(); |
| f_init_vty_bsc(); |
| |
| T.start; |
| alt { |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive { repeat; } |
| [] T.timeout { |
| setverdict(pass); |
| } |
| } |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| |
| /* Verify the CBCH load indications of an idle cell (without CBCH load) */ |
| function f_TC_cbc_load_idle() runs on test_CT { |
| template integer tr_slot_count := (2 .. 15); |
| const integer min_load_ind := 4; |
| var integer basic_count := 0; |
| var integer extd_count := 0; |
| var ASP_RSL_Unitdata rx_ud; |
| timer T := 10.0; |
| |
| f_init(); |
| |
| RSL_CCHAN.clear; |
| T.start; |
| alt { |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC(false, tr_slot_count))) { |
| basic_count := basic_count + 1; |
| repeat; |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD(false, tr_slot_count))) -> value rx_ud { |
| extd_count := extd_count + 1; |
| repeat; |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive { repeat; } |
| [] T.timeout { |
| if ((basic_count >= min_load_ind) and (extd_count >= min_load_ind)) { |
| setverdict(pass); |
| } else { |
| setverdict(fail, "Insufficient number of CBCH LOAD IND: ", |
| "BASIC=", basic_count, " EXTD=", extd_count); |
| } |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| testcase TC_cbc_sdcch4_load_idle() runs on test_CT { |
| f_init_vty_bsc(); |
| f_vty_cbch_setup(valueof(ts_RslChanNr_CBCH4(0))); |
| f_TC_cbc_load_idle(); |
| } |
| testcase TC_cbc_sdcch8_load_idle() runs on test_CT { |
| f_init_vty_bsc(); |
| f_vty_cbch_setup(valueof(ts_RslChanNr_CBCH8(2))); |
| f_TC_cbc_load_idle(); |
| } |
| |
| /* Verify CBCH overload indications are sent when sending too many SMS CB CMD */ |
| function f_TC_cbc_load_overload(CbchTestPars pars) runs on test_CT { |
| template integer tr_slot_count_basic := (11 .. 13); |
| template integer tr_slot_count_extd := (2 .. 15); |
| const integer min_load_ind := 4; |
| var integer basic_count := 0; |
| var integer extd_count := 0; |
| var ASP_RSL_Unitdata rx_ud; |
| timer T_total := 10.0; |
| timer T_retransmit := 0.2; |
| timer T_initial_guard := 2.0; |
| var integer i; |
| |
| f_init(); |
| |
| /* send tons of SMSCB Command */ |
| for (i := 0; i < 30; i := i+1) { |
| f_smscb_setup_rsl_chan(pars.basic); |
| } |
| |
| /* keep sending SMSCB Commands for another two seconds */ |
| T_initial_guard.start; |
| T_retransmit.start; |
| alt { |
| [] T_retransmit.timeout { |
| f_smscb_setup_rsl_chan(pars.basic); |
| T_retransmit.start; |
| repeat; |
| } |
| [] T_initial_guard.timeout { } |
| } |
| /* clear any pending messages (where load may not have peaked yet) */ |
| RSL_CCHAN.clear; |
| |
| /* keep sending SMSCB Commands while verifying LOAD INDICATIONS */ |
| T_total.start; |
| T_retransmit.start; |
| alt { |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC(true, tr_slot_count_basic))) { |
| basic_count := basic_count + 1; |
| repeat; |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD(false, tr_slot_count_extd))) { |
| extd_count := extd_count + 1; |
| repeat; |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { |
| setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); |
| } |
| [] RSL_CCHAN.receive { repeat; } |
| [] T_retransmit.timeout { |
| f_smscb_setup_rsl_chan(pars.basic); |
| T_retransmit.start; |
| repeat; |
| } |
| [] T_total.timeout { |
| if ((basic_count >= min_load_ind) and (extd_count >= min_load_ind)) { |
| setverdict(pass); |
| } else { |
| setverdict(fail, "Insufficient number of CBCH LOAD IND: ", |
| "BASIC=", basic_count, " EXTD=", extd_count); |
| } |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| testcase TC_cbc_sdcch4_load_overload() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(0)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm)), |
| extended := omit |
| }; |
| |
| f_init_vty_bsc(); |
| f_vty_cbch_setup(pars.chan_nr); |
| f_TC_cbc_load_overload(pars); |
| } |
| testcase TC_cbc_sdcch8_load_overload() runs on test_CT { |
| var CbchTestPars pars := { |
| chan_nr := valueof(ts_RslChanNr_CBCH4(2)), |
| basic := valueof(t_CbchPC(msgs_1m_3b_norm)), |
| extended := omit |
| }; |
| |
| f_init_vty_bsc(); |
| f_vty_cbch_setup(pars.chan_nr); |
| f_TC_cbc_load_overload(pars); |
| } |
| |
| |
| private template GsmRrMessage tr_PagingType1 := { |
| header := t_RrHeader(PAGING_REQUEST_TYPE_1, ?), |
| payload :=? |
| }; |
| |
| /* we expect four blocks of 14 bytes, let's fill them with content easily distinguishable */ |
| const octetstring c_etws_seg0 := '000102030405060708090a0b0c0d'O; |
| const octetstring c_etws_seg1 := '101112131415161718191a1b1c1d'O; |
| const octetstring c_etws_seg2 := '202122232425262728292a2b2c2d'O; |
| const octetstring c_etws_seg3 := '303132333435363738393a3b3c3d'O; |
| const octetstring c_etws := c_etws_seg0 & c_etws_seg1 & c_etws_seg2 & c_etws_seg3; |
| |
| /* Ensure only Paging Type 1 with segmented ETWS Primary Notification are sent after RSL_OSMO_ETWS_CMD */ |
| testcase TC_etws_p1ro() runs on test_CT { |
| /* decoding the actual entire P1 rest octets by manually generated code is |
| * too much effort; instead simply do a binary compare to this constant */ |
| const bitstring c_P1RO_hdr := '00101011101'B; |
| var integer seg_received[4] := { 0, 0, 0, 0 }; |
| var L1ctlDlMessage dl; |
| timer T := 10.0; |
| |
| f_init(); |
| f_init_l1ctl(); |
| f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); |
| |
| RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); |
| /* wait for a bit until old non-ETWS Paging messages are gone */ |
| f_sleep(1.0); |
| L1CTL.clear; |
| T.start; |
| alt { |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { |
| var GsmRrMessage l3 := dec_GsmRrMessage(dl.payload.data_ind.payload); |
| select (l3) { |
| case (tr_PAG_REQ1(tr_MI_LV(t_MI_NoIdentity()))) { |
| var octetstring p1ro := l3.payload.pag_req_1.rest_octets; |
| var bitstring midamble := oct2bit(substr(p1ro, 0, 3)); |
| var octetstring segment := substr(p1ro, 3, lengthof(p1ro)-3); |
| var BIT1 not_first := substr(midamble, 11, 1); |
| var integer seg_nr := bit2int(substr(midamble, 12, 4)); |
| var boolean err := false; |
| if (substr(midamble, 0, 11) != c_P1RO_hdr) { |
| setverdict(fail, "Received unexpected P1 RO header ", midamble); |
| } |
| if (not_first == '1'B) { |
| select (seg_nr) { |
| case (2) { |
| if (segment != c_etws_seg1) { |
| err := true |
| } else { |
| seg_received[1] := seg_received[1] + 1; |
| }} |
| case (3) { |
| if (segment != c_etws_seg2) { |
| err := true |
| } else { |
| seg_received[2] := seg_received[2] + 1; |
| }} |
| case (4) { |
| if (segment != c_etws_seg3) { |
| err := true |
| } else { |
| seg_received[3] := seg_received[3] + 1; |
| }} |
| case else { setverdict(fail, "Unknown segment Nr ", seg_nr); } |
| } |
| if (err) { |
| setverdict(fail, "Unexpected segment ", seg_nr, ": ", segment); |
| } |
| } else { |
| if (seg_nr != 4) { |
| setverdict(fail, "Invalid number of segments ", seg_nr); |
| err := true; |
| } |
| if (segment != c_etws_seg0) { |
| setverdict(fail, "Invalid first segment ", segment); |
| err := true; |
| } |
| if (not err) { |
| seg_received[0] := seg_received[0] + 1; |
| } |
| } |
| } |
| case (tr_PagingType1) { |
| setverdict(fail, "Received unexpected PAGING TYPE 1: ", l3); |
| } |
| } |
| repeat; |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout { |
| setverdict(pass); |
| } |
| } |
| log("Quantity of received ETWS PN segments: ", seg_received); |
| var integer i; |
| for (i := 0; i < 4; i := i+1) { |
| if (seg_received[i] < 15) { |
| setverdict(fail, "Segment ", i, " not received often enough"); |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| |
| /* Ensure only Paging Type 1 without ETWS Primary Notification are sent after disabling them */ |
| testcase TC_etws_p1ro_end() runs on test_CT { |
| /* we expect four blocks of 14 bytes, let's fill them with content easily |
| * distinguishable */ |
| /* decoding the actual entire P1 rest octets by manually generated code is |
| * too much effort; instead simply do a binary compare to this constant */ |
| const bitstring c_P1RO_hdr := '00101011101'B; |
| var L1ctlDlMessage dl; |
| timer T := 10.0; |
| |
| f_init(); |
| f_init_l1ctl(); |
| f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); |
| |
| RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); |
| /* wait for a bit until old non-ETWS Paging messages are gone */ |
| f_sleep(3.0); |
| /* disable the ETWS PN again */ |
| RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(''O))); |
| f_sleep(2.0); |
| T.start; |
| L1CTL.clear; |
| alt { |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { |
| var GsmRrMessage l3 := dec_GsmRrMessage(dl.payload.data_ind.payload); |
| select (l3) { |
| case (tr_PAG_REQ1(tr_MI_LV(t_MI_NoIdentity()))) { repeat; } |
| case (tr_PagingType1) { |
| setverdict(fail, "Received non-empty PT1 after disabling ETWS PN: ", l3); |
| } |
| } |
| repeat; |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout { |
| setverdict(pass); |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| |
| /* Ensure ETWS Primary Notification is passed from RSL to PCU interface */ |
| testcase TC_etws_pcu() runs on test_CT { |
| timer T := 10.0; |
| |
| f_init_with_pcuif(); |
| f_init_l1ctl(); |
| f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); |
| |
| RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); |
| |
| T.start; |
| alt { |
| [] PCU.receive(t_SD_PCUIF(g_pcu_conn_id, tr_PCUIF_APP_INFO_REQ(0, 0, c_etws))) { |
| setverdict(pass); |
| } |
| [] PCU.receive(t_SD_PCUIF(g_pcu_conn_id, tr_PCUIF_APP_INFO_REQ(?, ?, ?))) { |
| setverdict(fail, "PCU socket received invalid APP INFO"); |
| } |
| [] PCU.receive { repeat; } |
| [] T.timeout { |
| setverdict(fail, "PCU socket timeout receiving APP INFO (ETWS)"); |
| } |
| } |
| |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__); |
| } |
| |
| |
| |
| /* SMSCB TODO: |
| * multiple SMS BC CMD at the same time: Ensure all of them are sent exactly once |
| * extended CBCH vs. normal CBCH |
| * |
| */ |
| |
| control { |
| execute( TC_cbch_load_idle_no_cbch() ); |
| |
| execute( TC_sms_cb_cmd_sdcch4_1block() ); |
| execute( TC_sms_cb_cmd_sdcch4_2block() ); |
| execute( TC_sms_cb_cmd_sdcch4_3block() ); |
| execute( TC_sms_cb_cmd_sdcch4_4block() ); |
| execute( TC_sms_cb_cmd_sdcch4_multi() ); |
| execute( TC_sms_cb_cmd_sdcch4_schedule() ); |
| execute( TC_sms_cb_cmd_sdcch4_default_only() ); |
| execute( TC_sms_cb_cmd_sdcch4_default_and_normal() ); |
| execute( TC_sms_cb_cmd_sdcch4_default_then_null() ); |
| execute( TC_cbc_sdcch4_load_idle() ); |
| execute( TC_cbc_sdcch4_load_overload() ); |
| |
| execute( TC_sms_cb_cmd_sdcch8_1block() ); |
| execute( TC_sms_cb_cmd_sdcch8_2block() ); |
| execute( TC_sms_cb_cmd_sdcch8_3block() ); |
| execute( TC_sms_cb_cmd_sdcch8_4block() ); |
| execute( TC_sms_cb_cmd_sdcch8_multi() ); |
| execute( TC_sms_cb_cmd_sdcch8_schedule() ); |
| execute( TC_sms_cb_cmd_sdcch8_default_only() ); |
| execute( TC_sms_cb_cmd_sdcch8_default_and_normal() ); |
| execute( TC_cbc_sdcch8_load_idle() ); |
| execute( TC_cbc_sdcch8_load_overload() ); |
| |
| execute( TC_etws_p1ro() ); |
| execute( TC_etws_p1ro_end() ); |
| execute( TC_etws_pcu() ); |
| } |
| |
| |
| } |